R/Framework.R

Defines functions prependProcess findProcess hasBeenRun getreplaceArgsList runProcesses purgeOutput getFolderDepth getOutputDepthOne getOutputDepth writeProcessOutputRDS2 writeProcessOutputRDS1 getOutputElementType readOutputClass getOutputClass writeProcessOutputElementsRDS writeProcessOutputMemoryFiles getRelevantClass isValidOutputDataClass areAllValidOutputDataClasses unlistOneStep unlistToDataType removeIDsFromGeojson ggsaveApplyDefaults reportFunctionOutputOne writeProcessOutputTextFile getProcessOutputTextFilePath getProcessIDFromFunctionName getProcessNameFromProcessID getProcessIDByOffset getProcessIndexFromProcessID getProcessIDFromProcessName getProcessOutputFolder deleteProcessOutput getProcessOutputElements getProcessOutputTableNames listMemoryFiles getFilesRecursiveWithOrder getProcessOutputFiles flattenProcessOutput readProcessOutputFile getModelData unlistProcessOutput is.listOfOneList getProcessPlotOutput getProcessGeoJsonOutput getProcessTableOutput getProcessOutput getFunctionInputData getFunctionArguments setUseProcessData runProcess isProcess rearrangeProcesses duplicateProcess removeProcess expandProcess addProcess addProcesses addEmptyProcess createProcessIDString createNewProcessID writeMaxProcessIntegerID getMaxProcessIntegerID getNewDefaultProcessName is.convertableToVector is.convertableToTable parseParameter escapeTab escapeNewLine escapeTabAndNewline convertStringToNA simplifyListReadFromJSON formatProcessDataOne formatProcessData formatFunctionParameters formatFunctionInputs formatProcessParameters formatProcessName formatFunctionName formatProcess removeProcesses modifyProjects modifyProject modifyModel modifyProcess getAbsolutePath getAbsolutePaths getRelativePaths getRelativePath convertToRelativePaths detectFilePaths modifyProcessData modifyProcessParameters emptyFunctionInputs modifyFunctionInputs emptyFunctionParameters modifyFunctionParameters emptyProcessName modifyProcessName modifyFunctionName setListElements checkProcessName processNameExists onlyValidCharactersInProcessnName setFunctionName applyEmptyFunction getPossibleProcessParameterNames createEmptyProcess isFunctionInput checkFunctionInputs checkFunctionInput getProcessIDsFromBeforeAfter getProcessAndFunctionNames getProcessesSansProcessData scanForModelError getProcessTable modifyProcessNameInFunctionInputs modifyProcessNameInProcessIndexTable rearrangeProcessIndexTable removeFromProcessIndexTable addToProcessIndexTable writeProcessIndexTable matchProcesses readProcessIndexTable checkDataType getDataType getProcess getProcessArguments getProcessData getProcessParameters getProcessName getFunctionParameters getFunctionInputs getFunctionName getFunctionParameterFormats getStoxFunctionParameterTypes getStoxFunctionParameterPrimitiveTypes getStoxFunctionParameterDefaults getStoxFunctionParameterFormals getStoxFunctionParameterPossibleValues isBootstrapNetCDF4Function isBootstrapFunction isProcessDataFunction extractArgumentsToShow getArgumentsToShow getStoxFunctionMetaData getAvailableStoxFunctionNames unwrapProjectMemoryFile unReDoProject writeProjectMemoryIndex readProjectMemoryIndex splitProjectMemoryList deleteProjectMemoryFile removeFromArgumentFileTable insertToArgumentFileTable saveArgumentFile addTimeToFileName getNewProjectMemoryFileSansExt getNewArgumentFileSansExt appendProjectDescription resetModel writeActiveProcessIDFromTable writeActiveProcessID isActiveProcess getActiveProcess resolveProjectPath setSavedStatus setNotRunning setRunning isRunning initiateActiveProcessID buildSpatialFileReferenceString readCharAll replaceSpatialFileReference convertProcessDataToGeojson getDependentPackageVersion getPackageVersion addProjectDescriptionAttributes orderProjectDescription orderEachProcess writeProjectDescriptionJSON writeProjectDescription JSON2processData convertToPOSIX is.ConvertableToPOSIX convertToPosixInDataTable validateProjectDescriptionFile readProjectDescriptionJSON readProjectDescription isOpenProject isSaved isProjectOne isProject deleteProcessData deleteProject copyProject saveAsProject saveProject closeProject openProjectAsTemplate openProject createProject createProjectSessionFolderStructure createProjectSkeleton defineProcessIDs getTemplate getAvaiableTemplates validateFunction getPackageNameFromFunctionName getPackageFunctionName getFunctionNameFromPackageFunctionName getPackageNameFromPackageFunctionName validateStoxLibraryPackage addMissingAttributes getStoxFunctionAttributes getProjectPaths

Documented in addProcess closeProject copyProject createProject deleteProject duplicateProcess expandProcess findProcess getActiveProcess getAvaiableTemplates getAvailableStoxFunctionNames getModelData getProcess getProcessAndFunctionNames getProcessesSansProcessData getProcessGeoJsonOutput getProcessIDByOffset getProcessIDFromFunctionName getProcessIDFromProcessName getProcessIndexFromProcessID getProcessNameFromProcessID getProcessOutput getProcessOutputElements getProcessOutputFiles getProcessOutputFolder getProcessOutputTableNames getProcessPlotOutput getProcessTable getProcessTableOutput getProjectPaths getTemplate initiateActiveProcessID isOpenProject isProject isRunning isSaved modifyModel modifyProcess modifyProject modifyProjects openProject openProjectAsTemplate parseParameter prependProcess purgeOutput readProcessOutputFile readProjectDescription rearrangeProcesses removeProcess removeProcesses resetModel runProcess runProcesses saveAsProject saveProject scanForModelError setNotRunning setRunning unlistToDataType unReDoProject writeActiveProcessID writeProjectDescription writeProjectDescriptionJSON

#' This function gets the paths defined by \code{\link{initiateRstoxFramework}}.
#' 
#' @inheritParams general_arguments
#' @param name A string naming the path element to get. Set this to NULL to get all paths.
#' 
#' @export
#' 
getProjectPaths <- function(projectPath, name = NULL) {
    # Paste the project path to the relevant folders:
    paths <- getRstoxFrameworkDefinitions("paths")
    # Add the project path to all paths:
    paths <- lapply(paths, function(x) if(is.list(x)) lapply(x, function(y) file.path(projectPath, y)) else structure(file.path(projectPath, x), names = names(x)))
    if(length(name)) {
        paths <- paths[[name]]
    }
    paths
}

# Function for extracting the stoxFunctionAttributes of the package, and adding the package name and full function name (packageName::functionName) to each element (function) of the list.
getStoxFunctionAttributes <- function(packageName, requestedFunctionAttributeNames = NULL) {
    
    # Get the exported object 'stoxFunctionAttributes' from the package:
    if(!identical(packageName, "RstoxFramework")) {
        stoxFunctionAttributes <- tryCatch(
            getExportedValue(packageName, "stoxFunctionAttributes"), 
            error = function(err) NULL
        )
    }
    
    # Add function and package name:
    stoxFunctionAttributes <- lapply(stoxFunctionAttributes, append, list(packageName = packageName))
    stoxFunctionAttributes <- mapply(
        append, 
        stoxFunctionAttributes, 
        lapply(names(stoxFunctionAttributes), function(x) list(functionName = paste(packageName, x, sep = "::"))), 
        SIMPLIFY = FALSE
    )
    
    # Add the requested attributes if missing:
    if(length(requestedFunctionAttributeNames)) {
        stoxFunctionAttributes <- addMissingAttributes(stoxFunctionAttributes, requestedFunctionAttributeNames = requestedFunctionAttributeNames)
    }
    
    # Add the argument descriptions:
    argumentDescriptionFile <- system.file("extdata", "functionArguments.rds", package = packageName)
    if(file.exists(argumentDescriptionFile)) {
        
        # Read the argument descriptions:
        argumentDescriptions <- readRDS(argumentDescriptionFile)
        # Keep only the argument descriptions for functions given in the stoxFunctionAttributes:
        argumentDescriptions <- argumentDescriptions[names(argumentDescriptions) %in% names(stoxFunctionAttributes)]
        
        for(functionName in names(argumentDescriptions)) {
            stoxFunctionAttributes [[functionName]] [["functionArgumentDescription"]] <- argumentDescriptions [[functionName]]
        }
    }
    else {
        warning("StoX: The file ", argumentDescriptionFile, " does not exist.")
    }
    
    stoxFunctionAttributes
}

# Function to add the missing attributes of all functions.
addMissingAttributes <- function(stoxFunctionAttributes, requestedFunctionAttributeNames) {
    # Function to add the missing attributes on one function:
    addMissingAttributes_one <- function(stoxFunctionAttribute, requestedFunctionAttributeNames) {
        # Declare a list of empty elements named with the requested attributes:
        out <- vector("list", length(requestedFunctionAttributeNames))
        names(out) <- requestedFunctionAttributeNames
        # Get the names of the present attributes:
        presentNames <- intersect(names(stoxFunctionAttribute), requestedFunctionAttributeNames)
        # Insert the present attributes:
        out[presentNames] <- stoxFunctionAttribute[presentNames]
        out
    }
    
    # Add the missing attributes from all functions:
    stoxFunctionAttributes <- lapply(stoxFunctionAttributes, addMissingAttributes_one, requestedFunctionAttributeNames = requestedFunctionAttributeNames)
    stoxFunctionAttributes
}














# Function for validating a StoX function library package.
validateStoxLibraryPackage <- function(packageName) {
    
    if(identical(packageName, "RstoxFramework")) {
        return(TRUE)
    }
    
    # Get the StoX function attributes:
    stoxFunctionAttributes <- getStoxFunctionAttributes(packageName)
    
    # Return FALSE if the stox funciton attributes list does not exist:
    if(length(stoxFunctionAttributes) == 0) {
        warning("StoX: The package ", packageName, " does not export the required object 'stoxFunctionAttributes'.")
        return(FALSE)
    }
    
    # Check that all of the StoX functions are exported:
    exports <- getNamespaceExports(getNamespace(packageName))
    stoxFunctionNames <- names(stoxFunctionAttributes)
    stoxFunctionNamesPresent <- stoxFunctionNames %in% exports
    if(!all(stoxFunctionNamesPresent)) {
        warning("StoX: The package ", packageName, " specifies functions in the 'stoxFunctionAttributes' object that are not exported:\n", paste(stoxFunctionNames[!stoxFunctionNamesPresent], collapse = ", "))
        return(FALSE)
    }
    
    # Check that all the exported process data functions have a valid JSON schema:
    processDataSchema <- readProcessDataSchema(packageName)
    processDataSchemaNames <- names(processDataSchema)
    exportedProcessDataFunctions <- names(stoxFunctionAttributes)[sapply(stoxFunctionAttributes, "[[", "functionType") == "processData"]
    exportedProcessData <- sapply(stoxFunctionAttributes[exportedProcessDataFunctions], "[[", "functionOutputDataType")
    #exportedProcessDataFunctionsSansDefine <- sub("Define", "", exportedProcessDataFunctions)
    
    #if(!all(exportedProcessDataFunctionsSansDefine %in% processDataSchemaNames)) {
    if(!all(exportedProcessData %in% processDataSchemaNames)) {
        missingJSONs <- setdiff(exportedProcessData, processDataSchemaNames)
        warning("StoX: The package ", packageName, " exports processData functions specified in the 'stoxFunctionAttributes' object for which the processData is not documented with a JSON schema in the processDataSchema.json file:\n", paste(missingJSONs, collapse = ", "))
        #return(FALSE)
    }
    
    # Check that if any functions have format specified, the object "processPropertyFormats" must be exported:
    if(any(sapply(stoxFunctionAttributes, function(x) length(x$functionParameterFormat) && !all(unlist(x$functionParameterFormat)  == "none")))) {
        if(!"processPropertyFormats" %in% exports) {
            warning("StoX: The package ", packageName, " does not export the required object 'processPropertyFormats'.")
            return(FALSE)
        }
    }
    
    # Add more checks...
    
    TRUE
}

# Get the package name from the full adress to a function.
getPackageNameFromPackageFunctionName <- function(functionName) {
    if(!grepl("::", functionName, fixed = TRUE)) {
        warning("StoX: The function must be given as packageName::functionName. Was \"", functionName, "\".")
    }
    sub("\\::.*", "", functionName)
}
# Get the function name from the full adress to a function.
getFunctionNameFromPackageFunctionName <- function(functionName) {
    functionName <- substring(functionName, regexpr("::", functionName) + 2)
    if(length(functionName) == 0) {
        return("")
    }
    else {
        functionName
    }
}
# Get the full adress to a function.
getPackageFunctionName <- function(functionName) {
    if(grepl("::", functionName, fixed = TRUE)) {
        return(functionName)
    }
    stoxLibrary <- getRstoxFrameworkDefinitions("stoxLibrary")
    if(functionName %in% names(stoxLibrary)) {
        stoxLibrary[[functionName]]$functionName
    }
    else {
        ""
    }
}
# Get the package name from the function name.
getPackageNameFromFunctionName <- function(functionName) {
    getPackageNameFromPackageFunctionName(getPackageFunctionName(functionName))
}


# Function to check that the functionName refers to a valid funciton, i.e., that the function is exported from a valid package (see validateStoxLibraryPackage()), and that it is represented in the associated stoxFunctionAttributes list of that package.

validateFunction <- function(functionName) {
    
    # Expand the funciton name:
    packageFunctionName <- getPackageFunctionName(functionName)
    
    # 1. Check first that the function name contains a double colon, which is the first requirement for a process:
    if(length(packageFunctionName) == 0) {
        warning("StoX: The function \"", packageFunctionName, "\" does not appear to be a string of the form PACKAGENAME::FUNCTIONNAME, where PACKAGENAME is the package exporting the function with name FUNCTIONNAME.")
    }
    
    # Extract the packageName:
    packageName <- getPackageNameFromPackageFunctionName(packageFunctionName)
    
    # 2. Validate the package for use in the process:
    if(packageName %in% getRstoxFrameworkDefinitions("officialStoxLibraryPackagesAll")) {
    #if(validateStoxLibraryPackage(packageName)) {
        packageFunctionName
    }
    else {
        warning("StoX: Invalid function \"", functionName, "\"")
        functionName
    }
}








#' Get avilable templates.
#' 
#' @param list.out Logical: If TRUE return a list of the full descriptions of the templates.
#' 
#' @export
#'
getAvaiableTemplates <- function(list.out = FALSE) {
    # Get the templates:
    out <- getRstoxFrameworkDefinitions("stoxTemplates")
    # Return only the names if specified:
    if(!list.out) {
        out <- names(out)
    }
    out
}

#' Get a template.
#' 
#' @param template A string naming the template to use.
#' 
#' @export
#'
getTemplate <- function(template) {
    # Support reading a project description:
    if(isProject(template)) {
        template <- readProjectDescription(projectPath, verbose = verbose)$projectDescription
    }
    else {
        # Get the templates:
        templates <- getAvaiableTemplates(list.out = TRUE)
        if(template %in% names(templates)) {
            template <- templates[[template]]
        }
        else {
            warning("StoX: Invalid template name ", template, ". Available templates are ", paste0(names(templates), collapse = ", "))
        }
    }
    
    
    # Define the process IDs and return the template:
    defineProcessIDs(template)
}


# Set the processIDs to a project description object.
defineProcessIDs <- function(projectMemory) {
    # Define the process IDs:
    numProcessesPerModel <- sapply(projectMemory, length)
    integerIDsPerModel <- lapply(numProcessesPerModel, seq_len)
    processIDs <- lapply(integerIDsPerModel, createProcessIDString)
    
    # Set the processIDs as names to the processes of the models:
    for(thisname in names(projectMemory)) {
        names(projectMemory[[thisname]]) <- processIDs[[thisname]]
    }
    
    projectMemory
}






##################################################
##################################################
#' Create the StoX directories
#' 
#' This function creates the "stox" folder and the "project" and "reference" sub folders.
#' 
#' @inheritParams general_arguments
#' 
#' @return
#' A list of paths to the "stox" folder and sub folders.
#' 
#' @noRd
#' @seealso Use \code{\link{getStoxSkeletonPaths}} to get the folder paths.
#' 
createProjectSkeleton <- function(projectPath, ow = FALSE) {
    
    # Check whether the project exists:
    if(dir.exists(projectPath)) {
        if(!ow) {
            stop("StoX: The folder '", projectPath, "' exists. Choose a different project path.")
        }
        else {
            unlink(projectPath, recursive = TRUE, force = TRUE)
        }
    }
    
    # Get the paths of the root directory and StoX skeleton:
    stoxFolderStructure <- getProjectPaths(projectPath, "stoxFolderStructure")
    # Create the folders:
    lapply(stoxFolderStructure, dir.create, showWarnings = FALSE, recursive = TRUE)
    
    # Return the paths:
    stoxFolderStructure
}
#' 
#' @noRd
#' 
createProjectSessionFolderStructure <- function(projectPath, showWarnings = FALSE) {
    # Create the project session folder structure:
    projectSessionFolderStructure <- getProjectPaths(projectPath, "projectSessionFolderStructure")
    lapply(projectSessionFolderStructure, dir.create, recursive = TRUE, showWarnings = showWarnings)
}


##################################################
##################################################
#' Create, open, close, save, saveAs, copy and delete a StoX project.
#' 
#' Create a StoX project using \code{createProject}; open an existing (un-opened) project using \code{openProject}, which involves creating files holding the memory of the project; close a project using  \code{closeProject}, which removes the memory files; save the project using \code{saveProject}, which saves the memory files to the project description file; make a copy using \code{copyProject} or \code{saveAsProject}, where the former closes the given project unsaved and opens the copy, or delete a project using \code{deleteProject}.
#' 
#' @inheritParams general_arguments
#' @param open              Logical: If TRUE open the project after creating it.
#' @param force             Logical: If TRUE reopen (close and then open) the project if already open.
#' @param reset             Logical: If TRUE reset each model to the start of the model.
#' @param save              Logical: If TRUE save the project before closing. Default (NULL) is to ask the user whether to save the project before closing.
#' @param force.save If no changes are made to the project, force save anyway. Overrides the \code{save} option.
#' @param saveIfAlreadyOpen Logical: If TRUE save the project before closing if already open and force is TRUE.
#' @param newProjectPath    The path to the copied StoX project.
#' @param verbose           Logical: If TRUE, print information to the console, e.g. about backward compatibility.
#' @param empty.output      Logical: If TRUE, do not include the output files when copying. This can also be a vector of names of the output folders to empty.
#' @param empty.memory      Logical: If TRUE, do not include the memory data files when copying. This can also be a vector of names of the memory data folders to empty.
#' @param empty.input       Logical: If TRUE, do not include the input files when copying. This can also be a vector of names of the input data folders to empty.
#' @param close Logical: (In \code{copyProject}) If TRUE, close the project after copying.
#' 
#' @name Projects
#' 
NULL
#' 
#' @export
#' @rdname Projects
#' 
createProject <- function(
    projectPath, 
    template = "EmptyTemplate", 
    ow = FALSE, 
    showWarnings = FALSE, 
    open = TRUE, 
    Application = R.version.string
) {
    
    # Get the template:
    thisTemplate <- getTemplate(template)
    
    # Create the project folder structure:
    projectSkeleton <- createProjectSkeleton(projectPath, ow = ow)
    
    # Create the project session folder structure:
    createProjectSessionFolderStructure(projectPath, showWarnings = showWarnings)
    # Set the active process ID to 0 for all models:
    initiateActiveProcessID(projectPath)
    
    # Set the project memory as the selected template:
    temp <- addProcesses(
        projectPath = projectPath, 
        projectMemory = thisTemplate, 
        returnProcessTable = FALSE, 
        archive = FALSE, 
        add.defaults = FALSE
    )
    
    # Store the changes:
    archiveProject(projectPath)
    
    # Save the project, close it, and open:
    saveProject(
        projectPath, 
        msg = FALSE, 
        Application = Application
    )
    
    if(!open) {
        closeProject(
            projectPath, 
            save = TRUE, 
            Application = Application
        )
    }
    
    # Return the project path project name and saved status:
    list(
        projectPath = projectPath, 
        projectName = basename(projectPath), 
        saved = isSaved(projectPath)
    )
}
#' 
#' @export
#' @rdname Projects
#' 
openProject <- function(
    projectPath, 
    showWarnings = FALSE, 
    force = FALSE, 
    reset = FALSE, 
    saveIfAlreadyOpen = FALSE, 
    verbose = FALSE
) {
    
    # Resolve the projectPath:
    projectPath <- resolveProjectPath(projectPath)
    if(!length(projectPath)) {
        return(list(
            projectPath = NA, 
            projectName = NA, 
            saved = NA
        ))
    }
    
    # If already open, reopen if force:
    if(!force && isOpenProject(projectPath)) {
        if(showWarnings) {
            warning("StoX: Project ", projectPath, " is already open.")
        }
        
        # Reset the active process if requested:
        if(reset) {
            stoxModelNames <- getRstoxFrameworkDefinitions("stoxModelNames")
            # When processID is not given, the model is reset to the start:
            lapply(stoxModelNames, function(modelName) resetModel(
                projectPath = projectPath, 
                modelName = modelName
                )
            )
        }
        
        out <- list(
            projectPath = projectPath, 
            projectName = basename(projectPath), 
            saved = isSaved(projectPath)
        )
        return(out)
    }
    # No need for Application here as saveIfAlreadyOpen should not be used by any Application:
    closeProject(projectPath, save = saveIfAlreadyOpen, msg = FALSE)
    
    
   
    if(length(projectPath) == 0) {
        warning("StoX: The selected projectPath is not a StoX project or a folder/file inside a StoX project.")
        return(NULL)
    }
    else if(verbose) {
        message("Opening project ", projectPath)
    }
    
    # Read the project description file:
    #projectDescription <- readProjectDescription(projectPath, type = type)
    
    temp <- readProjectDescription(projectPath, verbose = verbose)
    projectDescription <- temp$projectDescription
    saved <- temp$saved
    
    # Create the project session folder structure:
    createProjectSessionFolderStructure(projectPath, showWarnings = showWarnings)
    
    # Set the active process ID to 0 for all models:
    initiateActiveProcessID(projectPath)
    
    # Set the project memory:
    temp <- addProcesses(
        projectPath = projectPath, 
        #modelName = names(projectMemory), 
        projectMemory = projectDescription, 
        returnProcessTable = FALSE, 
        archive = FALSE, 
        add.defaults = FALSE
    )
    
    # Save the project description attributes:
    writeProjectDescriptionAttributes(projectPath, projectDescription = projectDescription)
    
    # Store the changes:
    archiveProject(projectPath)
    
    ### # Set the status of the projcet as saved. This must take place after adding the processes, since there saved is set to FALSE:
    setSavedStatus(projectPath, status = saved)
    
    # Return the project path project name and saved status:
    list(
        projectPath = projectPath, 
        projectName = basename(projectPath), 
        saved = isSaved(projectPath)
    )
}
#' 
#' @export
#' @rdname Projects
#' 
openProjectAsTemplate <- function(
        projectPath, 
        newProjectPath, 
        ow = FALSE#, 
        #keepFilterExpressions = FALSE
) {
    
    stop("Unfinished!!!!!!!!!!!!!!")
    
    # Check whether the project to be used as template exists:
    if(!dir.exists(projectPath) || !isProject(projectPath)) {
        stop("The path ", projectPath, " does not point to a StoX project.")
    }
    
    # Check whether the project exists:
    if(dir.exists(newProjectPath)) {
        if(!ow) {
            stop("StoX: The project '", newProjectPath, "' exists. Choose a different project path for the new project.")
        }
        else {
            unlink(newProjectPath, recursive = TRUE, force = TRUE)
        }
    }
    
    # Copy the project as a template:
    copyProject(projectPath, newProjectPath, ow = ow, empty.output = TRUE, empty.input = TRUE, close = FALSE)
    
    # Get processes with AcousticLayer, AcousticPSU, BioticLayer, BioticPSU, BioticAssignment:
    processDataToBeDeleted <- c("AcousticLayer", "AcousticPSU", "BioticLayer", "BioticPSU", "BioticAssignment")
    processDataToBeDeleted_processID <- unlist(mapply(
        getProcessIDFromFunctionName, 
        functionName = processDataToBeDeleted, 
        SIMPLIFY = FALSE, 
        MoreArgs = list(projectPath = newProjectPath, modelName = "baseline")
        )
    )
    # Delete the processData:
    mapply(
        deleteProcessData, 
        projectPath = newProjectPath, 
        modelName = "baseline", 
        processID = processDataToBeDeleted_processID
    )
    # Save the changes:
    saveProject(newProjectPath)
        
        
    return(newProjectPath)
}
#' 
#' @export
#' @rdname Projects
#' 
closeProject <- function(
    projectPath, 
    save = NULL, 
    force.save = FALSE, 
    Application = R.version.string,
    msg = TRUE
) {
    #  If force.save, set save to TRUE:
    if(force.save)  {
        save  = TRUE
    }
    
    # Check that the project has been saved:
    if(isOpenProject(projectPath)) {
        if(isTRUE(save)) {
            saveProject(
                projectPath, 
                msg = FALSE, 
                Application = Application, 
                force = force.save
            )
        }
        else if(is.character(save)) {
            saveProject(
                projectPath, 
                Application = Application, 
                msg = FALSE, 
                force = force.save
            )
        }
        else if(!isFALSE(save) && !isSaved(projectPath)) {
            answer <- readline(paste("The project", projectPath, "has not been saved.\nDo you with to save before closing (y/n)?"))
            if(identical(tolower(answer), "y")) {
                saveProject(
                    projectPath, 
                    Application = Application, 
                    msg = FALSE, 
                    force = force.save
                )
            }
        }
        # Delete the project session folder structure:
        projectSessionFolderStructure <- getProjectPaths(projectPath, "projectSessionFolder")
        unlink(projectSessionFolderStructure, recursive = TRUE, force = TRUE)
    }
    else if(msg){
        message("StoX: Project ", projectPath, " is already closed.")
    }
}
#' 
#' @export
#' @rdname Projects
#' 
saveProject <- function(
    projectPath, 
    force = FALSE, 
    Application = R.version.string, 
    msg = TRUE
) {
    
    if(isSaved(projectPath) && !force) {
        output <- list(
            projectPath = projectPath, 
            projectName = basename(projectPath), 
            saved = TRUE
        )
        
        # Give a message by default:
        if(msg) {
            message("The project ", projectPath, " has already been saved. Use force = TRUE to save anyhow.")
        }
        return(output)
    }
    
    # Get the current project description and save it to the project.JSON file:
    writeProjectDescription(
        projectPath = projectPath, 
        Application = Application, 
        optionalDependencies = TRUE
    )
    # Set the status of the projcet as saved:
    setSavedStatus(projectPath, status = TRUE)
    
    # Return the project path project name and saved status:
    output <- output <- list(
        projectPath = projectPath, 
        projectName = basename(projectPath), 
        saved = isSaved(projectPath)
    )
    
    return(output)
}
#' 
#' @export
#' @rdname Projects
#' 
saveAsProject <- function(
    projectPath, 
    newProjectPath, 
    ow = FALSE, 
    Application = R.version.string
) {
    
    # Copy the current project and save it:
    copyProject(projectPath, newProjectPath, ow = ow)
    saveProject(newProjectPath, msg = FALSE, Application = Application)
    
    # Close the current project without saving (no need to Application here as save = FALSE)
    closeProject(projectPath, save = FALSE)
    
    # Return the project path project name and saved status:
    list(
        projectPath = newProjectPath, 
        projectName = basename(newProjectPath), 
        saved = isSaved(newProjectPath)
    )
}
#' 
#' @export
#' @rdname Projects
#' 
copyProject <- function(projectPath, newProjectPath, ow = FALSE, empty.output = FALSE, empty.input = FALSE, empty.memory = FALSE, close = FALSE, msg = TRUE) {
    if(ow) {
        unlink(newProjectPath, force = TRUE, recursive = TRUE)
    }
    
    #suppressWarnings(dir.create(newProjectPath, recursive = TRUE))
    createProjectSkeleton(newProjectPath, ow = ow)
    
    stoxModelFolders <- getRstoxFrameworkDefinitions("stoxModelFolders")
    stoxDataSourceFolders <- getRstoxFrameworkDefinitions("stoxDataSourceFolders")
    
    # Should input and output be emptied?:
    if(isTRUE(empty.output)) {
        empty.output <- stoxModelFolders
    }
    else if(isFALSE(empty.output)) {
        empty.output <- NULL
    }
    if(isTRUE(empty.input)) {
        empty.input <- stoxDataSourceFolders
    }
    else if(isFALSE(empty.input)) {
        empty.input <- NULL
    }
    if(isTRUE(empty.memory)) {
        empty.memory <- stoxModelFolders
    }
    else if(isFALSE(empty.memory)) {
        empty.memory <- NULL
    }
    
    
    toCopy <- RstoxFramework:::getRstoxFrameworkDefinitions("stoxFoldersList")
    
    if(length(empty.output)) {
        # Exlcude the models given in empty.output:
        toCopy$output <- file.path(toCopy$output, setdiff(stoxModelFolders, empty.output))
    }
    if(length(empty.input)) {
        toCopy$input <- file.path(toCopy$input, setdiff(stoxDataSourceFolders, empty.input))
    }
    if(length(empty.memory)) {
        # Specify what to keep in the projectSession folder ():
        toCopy$process <- c(
            file.path(toCopy$process, "project.json"), 
            file.path(toCopy$process, "projectSession", "status"), 
            file.path(toCopy$process, "projectSession", "memory"), 
            file.path(toCopy$process, "projectSession", "data", "models", setdiff(stoxModelFolders, empty.memory))
        )
    }
    
    toCopy <- unlist(toCopy)
    
    #lapply(list.dirs(projectPath, recursive = FALSE), file.copy, newProjectPath, recursive = TRUE)
    #lapply(toCopy, file.copy, newProjectPath, recursive = TRUE)
    
    # Get the folders to place the files in, relative to the new project:
    #newFolders <- sub(path.expand(projectPath), "", dirname(toCopy))
    
    
    # Remove trailing slash:
    newFolders <- gsub("^/", "", toCopy)
    newDirs <- dirname(file.path(newProjectPath, newFolders))
    
    temp <- lapply(newDirs, dir.create, showWarnings = FALSE, recursive = TRUE)
    toCopyFull <- file.path(projectPath, toCopy)
    temp <- mapply(file.copy, toCopyFull, newDirs, recursive = TRUE)
    #file.copy(projectPath, newProjectPath, recursive=TRUE)
    
    if(close) {
        closeProject(newProjectPath, save = FALSE, msg = msg)
    }
}
#' 
#' @export
#' @rdname Projects
#' 
deleteProject <- function(projectPath) {
    unlink(projectPath, force = TRUE, recursive = TRUE)
}
### #' 
### #' @export
### #' 
### resetProject <- function(projectPath) {
###     originalProjectDescription <- getOriginalProjectDescription(projectPath)
###     setCurrentProjectDescription(projectPath, projectDescription = originalProjectDescription)
###     saveProject(projectPath)
### }
deleteProcessData <- function(projectPath, modelName, processID) {
    modifyProcessData(
        projectPath, 
        modelName, 
        processID, 
        newProcessData = list(), 
        purge.processData = TRUE
    )
}


##################################################
##################################################

### @param projectDescription A list holding the project description, as read using \code{\link{readProjectDescription}}.


#' Utilities for projects.
#' 
#' @inheritParams general_arguments
#' @param projectDescriptionFile The path to the file holding the projectDescription.
#' @param applyBackwardCompatibility Logical: If TRUE apply backward compatibility actions when running \code{readProjectDescription}.
#' @param formatProcesses Logical: If TRUE format the processes after reading the projectDescription file, ensuring correct primitive types. This has a use of FALSE in \code{readModelData}, but should otherwise be set to TRUE.
#' @param validateJSON Logical: If TRUE validate the project.json.
#' @param strict Logical: If TRUE, require that all folders of the projectSession folder exist in isOpenProject(). Otherwise only require that the projectSession folder exists.
#' 
#' @name ProjectUtils
#' 
NULL
#' 
#' @export
#' @rdname ProjectUtils
#' 
isProject <- function(projectPath) {
    sapply(projectPath, isProjectOne)
}
# Checks only one project:
isProjectOne <- function(projectPath) {
    if(!is.character(projectPath)) {
        warning("projectPath must be a character string.")
        return(FALSE)
    }
    
    # If the project is zipped, read the paths without unzipping:
    if(tolower(tools::file_ext(projectPath)) == "zip") {
        files <- utils::unzip(projectPath, list = TRUE)
        projectName <- basename(tools::file_path_sans_ext(projectPath))
        projectNameWithSlash <- paste0(projectName, "/")
        if(!projectNameWithSlash %in% files$Name) {
            stop("A zipped StoX project must be a zip with the same name as the project contained in the zip (excluding file extension), such as testProject.zip for a project named testProject.")
        }
        else {
            stoxFolders <- unlist(getRstoxFrameworkDefinitions("stoxFoldersList"))
            stoxFoldersWithSlash <- paste0(projectNameWithSlash, stoxFolders, "/")
            valid <- all(stoxFoldersWithSlash %in% files$Name)
        }
    }
    else {
        existsFolders <- sapply(getProjectPaths(projectPath, "stoxFolders"), file.exists)
        valid <- length(existsFolders) && all(existsFolders)
    }
    
    return(valid)
}
#' 
#' @export
#' @rdname ProjectUtils
#' 
isSaved <- function(projectPath) {
    # Get the path to the projectSavedStatusFile:
    projectSavedStatusFile <- getProjectPaths(projectPath, "projectSavedStatusFile")
    # Missing file implies not saved:
    if(!file.exists(projectSavedStatusFile)) {
        FALSE
    }
    else {
        as.logical(readLines(projectSavedStatusFile, 1))
    }
}
#' 
#' @export
#' @rdname ProjectUtils
#' 
isOpenProject <- function(projectPath, strict = FALSE) {
    if(isProject(projectPath)) {
        if(strict) {
            activeProcessIDFile <- getProjectPaths(projectPath, "activeProcessIDFile")
            hasActiveProcessData <- file.exists(activeProcessIDFile)
            existsFolders <- sapply(getProjectPaths(projectPath, "projectSessionFolderStructure"), file.exists)
            hasActiveProcessData && length(existsFolders) && all(existsFolders)
        }
        else {
            file.exists(getProjectPaths(projectPath, "projectSessionFolder"))
        }
    }
    else {
        warning("StoX: Project ", projectPath, " does not exist. Please specify the path to the folder of a StoX project (containing the sub-folders input, output and process).")
        NA
    }
}



#' Read the project description.
#' 
#' @export
#' @rdname ProjectUtils
#' 
readProjectDescription <- function(
    projectPath, 
    verbose = FALSE, 
    projectDescriptionFile = NULL, 
    applyBackwardCompatibility = TRUE, 
    formatProcesses = TRUE, 
    validateJSON = TRUE
) {
    
    # Get the projectDescriptionFile path:
    if(!length(projectDescriptionFile)) {
        projectDescriptionFile <- getProjectPaths(projectPath, "projectJSONFile")
    }
    
    # If it does not exist, search for other project files:
    if(!file.exists(projectDescriptionFile)) {
        stop("StoX: The project description file ", projectDescriptionFile, " does not exist.")
    }    
    
    ### # Run the appropriate reading function:
    ### functionName <- "readProjectDescriptionJSON"
    ### projectDescription <- do.call(functionName, list(
    ###     projectDescriptionFile = projectDescriptionFile
    ### ))
    ### 
    # Read the project.json:
    projectDescription <- readProjectDescriptionJSON(projectDescriptionFile)
    
    # Make sure all models are present (at least as empty models): 
    missingModels <- setdiff(getRstoxFrameworkDefinitions("stoxModelNames"), names(projectDescription))
    for(modelName in missingModels) {
        projectDescription[[modelName]] <- list()
    }
    # Order the models:
    if(!all(names(projectDescription) == getRstoxFrameworkDefinitions("stoxModelNames"))) {
        projectDescription <- orderProjectDescription(projectDescription)
    }
    
    # Warning if not certified Rstox packages:
    if(!isTRUE(attr(projectDescription, "AllCertifiedRstoxPackageVersion"))) {
        warning(
            "StoX: The project was saved with uncertified Rstox package versions. This implies that reproducibility is not guaranteed. (", 
            "Used: ", 
            paste(attr(projectDescription, "RstoxPackageVersion"), collapse = ", "), 
            ". Certified: ", 
            paste(attr(projectDescription, "CertifiedRstoxPackageVersion"), collapse = ", "), 
            ".)"
        )
    }
    
    # Apply backward compatibility:
    saved <- TRUE
    if(applyBackwardCompatibility) {
        projectDescriptionAfterBackwardCompatibility <- applyBackwardCompatibility(projectDescription, verbose = verbose)
        # Set saved to TRUE if no backward compatibility actions were taken: 
        saved <- identical(projectDescription, projectDescriptionAfterBackwardCompatibility)
        #setSavedStatus(projectPath, status = saved)
        projectDescription <- projectDescriptionAfterBackwardCompatibility
    }
    
    # Format the processes, ensuring correct primitive types:
    if(formatProcesses) {
        for(modelName in names(projectDescription)) {
            for(processIndex in seq_along(projectDescription [[modelName]])) {
                projectDescription [[modelName]] [[processIndex]] <- formatProcess(projectDescription [[modelName]] [[processIndex]])
            }
        }
    }
    
    
    # Introduce process IDs: 
    projectDescription <- defineProcessIDs(projectDescription)
    
    
    # Validate the project.json here, and try to validate the project.json to be saved if the initial validation fails. 
    if(validateJSON) {
        valid <- validateProjectDescriptionFile(projectDescriptionFile)
        if(!isTRUE(valid)) {
            # Try writing to a tempfile and validating this file:
            tempProjectDescriptionFile <- tempfile()
            writeProjectDescription(
                projectDescription = projectDescription, 
                projectDescriptionFile = tempProjectDescriptionFile, 
                optionalDependencies = TRUE
            )
            valid <- validateProjectDescriptionFile(tempProjectDescriptionFile)
        }
        
        if(!isTRUE(valid)) {
            # Write the error to a temp file:
            tempErrorFile <- file.path(gsub("\\\\", "/", tempdir()), "projectJSONValidatorError.rds")
            saveRDS(valid, tempErrorFile)
            #stop("StoX: The file ", projectDescriptionFile, " is not a valid project.json file. \nRun the following code in R to see the JSON schema validation error:\n err <- readRDS(\"", tempErrorFile, "\")")
            warning("StoX: The JSON validation failed for the project description file ", projectDescriptionFile, ". The file was attempted opened anyway, but there may be errors in the project, and the results generally not be trusted. \nRun the following code in R to see the JSON schema validation error:\n err <- readRDS(\"", tempErrorFile, "\")")
        }
    }
    
    
    return(
        list(
            projectDescription = projectDescription, 
            saved = saved
        )
    )
}


readProjectDescriptionJSON <- function(projectDescriptionFile) {
    
    ### # Validate json object against schema
    ### browser()
    ### projectValidator <- getRstoxFrameworkDefinitions("projectValidator", engine = "ajv")
    ### valid <- projectValidator(projectDescriptionFile, verbose = TRUE)
    ### if(!isTRUE(valid)) {
    ###     message(
    ###         paste(
    ###             capture.output(
    ###                 projectValidator("~/Code/Github/RstoxFramework/RstoxFramework/inst/test/project.json", verbose = TRUE)
    ###                 ), 
    ###             collapse = "\n"
    ###         )
    ###     )
    ###     stop("StoX: The file ", projectDescriptionFile, " is not a valid project.json file.")
    ### }
    
    # Read project.json file to R list. Use simplifyVector = FALSE to preserve names:
    #projectDescriptionList <- jsonlite::read_json(projectDescriptionFile, simplifyVector = FALSE)
    projectDescriptionList <- tryCatch(
        jsonlite::read_json(projectDescriptionFile, simplifyVector = FALSE), 
        error = function(err) {
            stop("Unable to parse the StoX description file ", projectDescriptionFile, ", as jsonlite::read_json reported the error \n\n", err)
        }
    )
    
    # Add the headers as attributes:
    projectDescription <- projectDescriptionList$project$models
    attrs <- projectDescriptionList$project[! names(projectDescriptionList$project) %in% "models"]
    # Unlist all attributes, as these are vectors only and simplifyVector = FALSE was used when reading:
    attrs <- lapply(attrs, unlist)
    for(attrsName in names(attrs)) {
        attr(projectDescription, attrsName) <- attrs[[attrsName]]
    }
    
    ## Convert geojson to spatial object and list to data.table:
    #projectDescription <- convertProjectDescription(projectDescription)
    #
    ## Convert primitive type:
    #projectDescription <- convertPrimitiveType(projectDescription)
    
    return(projectDescription)
}





validateProjectDescriptionFile <- function(projectDescriptionFile, warn = TRUE) {
    # Validate the project description file against schema
    projectValidatorAJV <- getRstoxFrameworkDefinitions("projectValidatorAJV")
    valid <- projectValidatorAJV(projectDescriptionFile, verbose = TRUE)
    return(valid)
}





convertToPosixInDataTable <- function(x) {
    convertableToPOSIX <- unlist(x[, lapply(.SD, is.ConvertableToPOSIX)])
    if(any(convertableToPOSIX)) {
        DateTimeColumns <- names(x)[convertableToPOSIX]
        x[, (DateTimeColumns) := lapply(.SD, convertToPOSIX), .SDcols = DateTimeColumns]
    }
}





is.ConvertableToPOSIX <- function(x) {
    if(is.character(x)) {
        # Convert to POSIX:
        POSIX <- convertToPOSIX(x)
        any(!is.na(POSIX))
    }
    else {
        FALSE
    }
}


convertToPOSIX <- function(x) {
    # Get the DateTime format used by StoX:
    StoxDateTimeFormat <- getRstoxFrameworkDefinitions("StoxDateTimeFormat")
    StoxTimeZone <- RstoxData::getRstoxDataDefinitions("StoxTimeZone")
    
    # Convert to POSIX:
    POSIX <- as.POSIXct(x, format = StoxDateTimeFormat, tz = StoxTimeZone)
    
    return(POSIX)    
}



# Convert JSON string to a process data object.
JSON2processData <- function(JSON) {
    data <- jsonlite::fromJSON(JSON)
    if(is.list(data)) {
        if(!data.table::is.data.table(data)) {
            data <- lapply(data, data.table::as.data.table)
        }
        else if(all(c("type", "features") %in% names(data))) {
            # Do not convert as the object is already geojson???
        }
    }
    else {
        data <- data.table::as.data.table(data)
    }
   
    data
}



#' Write the project description.
#' 
#' @export
#' @param Application The application running StoX. Defaulted to the R.version.string, but can be a StoX version when run from the GUI.
#' @param projectDescription The project description to write to the project.json file. Defaulted to NULL, in which case the project description is read for the project.
#' @inheritParams getOfficialRstoxPackageVersion
#' @inheritParams ProjectUtils
#' 
writeProjectDescription <- function(projectPath, projectDescription = NULL, projectDescriptionFile = NULL, Application = R.version.string, optionalDependencies = FALSE, verbose = FALSE) {
    
    # Get the file to write to:
    if(!length(projectDescriptionFile)) {
        projectSessionFolder <- getProjectPaths(projectPath, "projectSessionFolder")
        if(!file.exists(projectSessionFolder)) {
            stop("StoX: The project memory folder ", projectSessionFolder, " does not exist. Project ", projectPath, " cannot be saved.")
        }
        
        projectDescriptionFile <- getProjectPaths(projectPath, "projectJSONFile")
    }
    
    
    # Get full project description:
    if(!length(projectDescription)) {
        # Include the attributes in order to apply the backward compatibility (in case Rstox-pakages changed whilst the project was open):
        projectDescription <- getProjectMemoryData(projectPath, modelName = "all", named.list = TRUE, addAttributes = TRUE)
    }
    
    
    # Unname the processes (removing processIDs):
    att <- attributes(projectDescription)
    projectDescription <- lapply(projectDescription, unname)
    attributes(projectDescription) <- att
    
    
    # NOTE: This was chanegd in the GUI to close the project and then reopen after installing Rstox packages:
    # Apply backward compatibility also when saving, as one can update the Rstox packages while a project is open(due to the memory files and not in RAM):
    #projectDescription <- applyBackwardCompatibility(projectDescription, verbose = verbose)
    
    # Add the attirbutes:
    projectDescription <- addProjectDescriptionAttributes(projectDescription, optionalDependencies = optionalDependencies)
    
    # Save the project.json:
    writeProjectDescriptionJSON(
        projectDescription = projectDescription, 
        projectDescriptionFile = projectDescriptionFile, 
        Application = Application
    )
}


##################################################
##################################################
#' Write project description to a file in json format
#' 
#' This function writes project description to json file.
#' 
#' @inheritParams general_arguments
#' @param projectDescription  a list of lists with project description.
#' @param projectDescriptionFile  a file name.
#' 
writeProjectDescriptionJSON <- function(projectDescription, projectDescriptionFile, Application) {
    
    # Order the argument of each process:
    projectDescription <- orderEachProcess(projectDescription)
    
    ## Drop non-character columns that contain NA, as these will be written as the string "NA" by toJSON_Rstox():
    #for(modelName in names(projectDescription)) {
    #    for(processIndex in seq_along(projectDescription[[modelName]])) {
    #        temp <- recodeNumericNAOneProcessData(
    #            projectDescription[[modelName]][[processIndex]]$processData, 
    #            na = getRstoxFrameworkDefinitions("jsonNA")
    #        )
    #    }
    #}
    
    # Convert spatial to geojson string, and write to temporary files for modifying the project.json to have geojson instead of geojson string: 
    projectDescription <- convertProcessDataToGeojson(projectDescription)
    
    # Add attributes and wrap the models into an object:
    projectDescriptionList <- list(
        project = list(
            TimeSaved = attr(projectDescription, "TimeSaved"), 
            RVersion = attr(projectDescription, "RVersion"),  
            RstoxPackageVersion = attr(projectDescription, "RstoxPackageVersion"), 
            CertifiedRstoxPackageVersion = attr(projectDescription, "CertifiedRstoxPackageVersion"), 
            AllCertifiedRstoxPackageVersion = attr(projectDescription, "AllCertifiedRstoxPackageVersion"), 
            OfficialRstoxFrameworkVersion = isOfficialRstoxFrameworkVersion(),
            DependentPackageVersion = attr(projectDescription, "DependentPackageVersion"), 
            Application = Application, 
            models = projectDescription # Do we need to remove the attributes???
        )
    )
    
    # Convert project description to json structure: 
    json <- toJSON_Rstox(projectDescriptionList)
    
    # Read any geojson objects stored in temporary file by convertProcessDataToGeojson():
    json <- replaceSpatialFileReference(json)
    
    # Fix pretty formatting by reading in and writing back the file:
    #write(json, projectDescriptionFile)
    #json <- toJSON_Rstox(jsonlite::read_json(projectDescriptionFile), pretty = TRUE)
    json <- jsonlite::prettify(json)
    
    # Validate the json structure with json schema
    ### projectValidator <- getRstoxFrameworkDefinitions("projectValidator")
    ### valid <- projectValidator(json)
    ### if(!isTRUE(valid)) {
    ###     cat("Output from project.json validator:\n")
    ###     print(projectValidator(json, verbose = TRUE))
    ###     stop("StoX: Cannot write the project.json file. It is not a valid project.json file.")
    ### }
    #jsonvalidate::json_validate(json)
    
    # 5. Write the validated json to file: 
    write(json, projectDescriptionFile) 
}



orderEachProcess <- function(projectDescription) {
    # Store the attributes:
    att <- attributes(projectDescription)
    
    # Order the arguments:
    projectDescription <- lapply(
        projectDescription, 
        function(model) lapply(
            model, 
            getRstoxFrameworkDefinitions("orderProcessArguments")
        )
    )
    
    # Reset attributes:
    attributes(projectDescription) <- att
    
    return(projectDescription)
}


orderProjectDescription <- function(projectDescription) {
    # Store the attributes:
    att <- attributes(projectDescription)
    
    # Order the project:
    projectDescription <- projectDescription[getRstoxFrameworkDefinitions("stoxModelNames")]
    
    # Reset attributes, but keep the new names:
    att$names <- names(projectDescription)
    attributes(projectDescription) <- att
    
    return(projectDescription)
}



addProjectDescriptionAttributes <- function(projectDescription, optionalDependencies = FALSE) {
    
    # Get packcage versions as strings "PACKAGENAME vPACKAGEVERSION":
    dependentPackageVersion <- getRstoxFrameworkDefinitions("dependentPackageVersion")
    
    # Get the certified Rstox package versions:
    certifiedRstoxPackageVersionList <- getOfficialRstoxPackageVersion(list.out = TRUE, optionalDependencies = optionalDependencies)
    
    
    # Paste the package name and version:
    CertifiedRstoxPackageVersion <- getPackageNameAndVersionString(
        certifiedRstoxPackageVersionList$packageName, 
        certifiedRstoxPackageVersionList$version
    )
    
    # Get installed versions:
    InstalledRstoxPackageVersion <- getRstoxFrameworkDefinitions("InstalledRstoxPackageVersion")
    
    # Get the all certified tag:
    AllCertifiedRstoxPackageVersion = identical(
        sort(CertifiedRstoxPackageVersion), 
        sort(InstalledRstoxPackageVersion)
    )
    
    # Gather and add the attributes:
    attrs <- list(
        TimeSaved = strftime(as.POSIXlt(Sys.time(), "UTC", "%Y-%m-%dT%H:%M:%S") , "%Y-%m-%dT%H:%M:%OS3Z"), 
        RVersion = R.version.string, 
        RstoxPackageVersion = InstalledRstoxPackageVersion, 
        CertifiedRstoxPackageVersion = CertifiedRstoxPackageVersion, 
        AllCertifiedRstoxPackageVersion = AllCertifiedRstoxPackageVersion, 
        DependentPackageVersion = dependentPackageVersion
    )
    for(attrsName in names(attrs)) {
        attr(projectDescription, attrsName) <- attrs[[attrsName]]
    }
    
    projectDescription
}



# Function to get the package version of several packages as strings:
getPackageVersion <- function(packageNames, only.version = FALSE, sep = "_") {
    # Changed from using utils::packageVersion, which returns a numeric sequence which when converted to character separates with dots only, ignoring any hyphens in the original package version, to getNamespaceVersion(), which gives us exactly what we need:
    #version <- sapply(packageNames, function(x) as.character(utils::packageVersion(x)))
    #version <- sapply(packageNames, getNamespaceVersion)
    # No, it is better to use installed.packages(), as it does not load the namespace:
    version <- getVersionStringOfPackage(packageNames)
    
    if(only.version) {
        version
    }
    else {
        getPackageNameAndVersionString(packageNames, version, sep = sep)
    }
}


# Get the versions of the dependent packages recursively:
#getDependentPackageVersion <- function(packageName, only.depedencies = TRUE) {
getDependentPackageVersion <- function(
    packageName, 
    dependencyTypes = c("Depends", "Imports", "LinkingTo"),  # Use the types explicitely, since the keyword "strong" was introduced in R 4.1, and will cause an error in R <= 4.0.
    recursive = TRUE
) {
    
    # Read the package table from the repos, using the first lib as the StoX GUI selects a folder in some cases on Windows and otherwise we can assume that the first should be used:
    packageTable <- as.data.frame(utils::installed.packages(.libPaths()[1]), stringsAsFactors = FALSE)
    
    # tools::package_dependencies has some problems with one row package table on R 4.0, and it seems fair to require more than one package in the table:
    if(NROW(packageTable) > 1) {
        # Get the dependencies:
        if(!all(dependencyTypes %in% c("Depends", "Imports", "LinkingTo", "Suggests", "Enhances"))) {
            stop("dependencyTypes must be among \"Depends\", \"Imports\", \"LinkingTo\", \"Suggests\", \"Enhances\"")
        }
        deps <- unique(unlist(tools::package_dependencies(packages = packageName, db = packageTable, recursive = recursive, which = dependencyTypes)))
        
        # Ignore base packages of R (but include recommended packages, which are also shipped with R but can be installed manually):
        deps <- setdiff(deps, rownames(utils::installed.packages(priority = "base")))
        
        # Ignore also Rstox packages:
        deps <- deps[!startsWith(deps, "Rstox")]
        
        
        # Get package versions as strings "PACKAGENAME vPACKAGEVERSION":
        dependentPackageVersion <- getPackageVersion(deps)
    }
    else {
        dependentPackageVersion <- NULL
    }
    
    
    return(dependentPackageVersion)
}



















convertProcessDataToGeojson <- function(projectDescription) {
    # Run through the processes and convert SpatialPolygonsDataFrame to geojson string:
    for(modelName in names(projectDescription)) {
        for(processIndex in seq_along(projectDescription [[modelName]])) {
            for(processDataIndex in names(projectDescription [[modelName]] [[processIndex]]$processData)) {
                this <- projectDescription [[modelName]] [[processIndex]]$processData[[processDataIndex]]
                #if("SpatialPolygonsDataFrame" %in% class(this)) {
                if("sf" %in% class(this)) {
                    #projectDescription [[modelName]] [[processIndex]]$processData[[processDataIndex]] <- geojsonio::geojson_json(this)
                    projectDescription [[modelName]] [[processIndex]]$processData[[processDataIndex]] <- buildSpatialFileReferenceString(this)
                }
            }
        }
    }
    
    return(projectDescription)
}


replaceSpatialFileReference <- function(x) {
    
    # Get the start and end position of geojson file paths:
    spatialFileReferenceCodeStart <- getRstoxFrameworkDefinitions("spatialFileReferenceCodeStart")
    spatialFileReferenceCodeEnd <- getRstoxFrameworkDefinitions("spatialFileReferenceCodeEnd")
    start <- unlist(gregexpr(spatialFileReferenceCodeStart, x))
    
    # Return unaltered if no hits:
    if(length(start) == 1 && start == -1) {
        return(x)
    }
    
    end <- unlist(gregexpr(spatialFileReferenceCodeEnd, x))
    spatialFile <- mapply(substr, x, start + nchar(spatialFileReferenceCodeStart), end - 1, USE.NAMES = FALSE)
    spatialString <- sapply(spatialFile, readCharAll)
    
    # Replace in reverse order to avoid messing up the start and end indices (DO NOT USE substring() here, as it will truncate the input, use substr instead):
    for(ind in rev(seq_along(spatialString))) {
        x <- paste0(
            substr(x, 1, start[ind] - 2), 
            spatialString[ind], 
            substr(x, end[ind] + nchar(spatialFileReferenceCodeEnd) + 1, nchar(x))
        )
    }
    
    return(x)
}

readCharAll <- function(spatialFile) {
    readChar(spatialFile, file.info(spatialFile)$size)
}





buildSpatialFileReferenceString <- function(x) {
    filePath <- tempfile()
    
    # Write the input SpatialPolygonsDataFrame to a temporary file, but use geojsonsf instead of geojsonio to reduce dependencies:
    #write(geojsonio::geojson_json(x), file = filePath)
    #write(geojsonsf::sf_geojson(sf::st_as_sf(x)), filePath)
    #write(geojsonsf::sf_geojson(sf::st_as_sf(x), simplify = FALSE), filePath)
    write(geojsonsf::sf_geojson(x, simplify = FALSE), filePath)
    
    SpatialFileReferenceString <- paste0(
        getRstoxFrameworkDefinitions("spatialFileReferenceCodeStart"), 
        filePath, 
        getRstoxFrameworkDefinitions("spatialFileReferenceCodeEnd")
    )
    return(SpatialFileReferenceString)
}


#' Initiate the actige processID.
#' 
#' @rdname ProjectUtils
#' 
initiateActiveProcessID <- function(projectPath) {
    # Read the active process ID for the model:
    activeProcessIDFile <- getProjectPaths(projectPath, "activeProcessIDFile")
    # Initiate with all zeros:
    #activeProcessIDTable <- data.table::as.data.table(matrix(NA, nrow = 1, ncol = 3))
    activeProcessIDTable <- data.table::data.table(
        modelName = getRstoxFrameworkDefinitions("stoxModelNames"), 
        processID = as.character(NA), 
        processDirty = NA, 
        propertyDirty = NA 
    )
    #colnames(activeProcessIDTable) <- getRstoxFrameworkDefinitions("stoxModelNames")
    data.table::fwrite(activeProcessIDTable, activeProcessIDFile, sep = "\t", na = "NA")
    activeProcessIDFile
}

#' Check or set if a project is running or not.
#' 
#' @export
#' @rdname ProjectUtils
#' 
isRunning <- function(projectPath, modelName) {
    modelIsRunningFile <- getProjectPaths(projectPath, "modelIsRunningFile")[[modelName]]
    file.exists(modelIsRunningFile)
}
#' 
#' @export
#' @rdname ProjectUtils
#' 
setRunning <- function(projectPath, modelName) {
    modelIsRunningFile <- getProjectPaths(projectPath, "modelIsRunningFile")[[modelName]]
    write("", modelIsRunningFile)
}
#' 
#' @export
#' @rdname ProjectUtils
#' 
setNotRunning <- function(projectPath, modelName) {
    modelIsRunningFile <- getProjectPaths(projectPath, "modelIsRunningFile")[[modelName]]
    unlink(modelIsRunningFile, force = TRUE)
}



# Set the saved status of the project.
setSavedStatus <- function(projectPath, status) {
    # Get the path to the projectSavedStatusFile:
    projectSavedStatusFile <- getProjectPaths(projectPath, "projectSavedStatusFile")
    # Write the status to the file:
    writeLines(as.character(status), projectSavedStatusFile)
}


# Get the project path possibly from a path in side the project.
resolveProjectPath <- function(filePath) {
    # Move up the folder hierarchy and find the project path:
    projectPath <- filePath
    while(!isProject(projectPath)) {
        up <- dirname(projectPath)
        if(up == projectPath) {
            warning("StoX: The file path ", filePath, " is not a StoX project or a file inside a StoX project.")
            return(NULL)
        }
        else {
            projectPath <- up
        }
    }
    projectPath
}

#' Get the active process.
#' 
#' @inheritParams general_arguments
#'
#' @export
#'
getActiveProcess <- function(projectPath, modelName = NULL) {
    # Read the active process ID for the model:
    activeProcessIDFile <- getProjectPaths(projectPath, "activeProcessIDFile")
    if(!file.exists(activeProcessIDFile)) {
        warning("StoX: The active process ID file has not been initiated.")
    }
    activeProcessIDTable <- data.table::fread(activeProcessIDFile, sep = "\t", encoding = "UTF-8")
    
    if(length(modelName)) {
        #return(activeProcessIDTable[[modelName]])
        thisModelName <- modelName
        output <- activeProcessIDTable[modelName == thisModelName, ]
        output <- as.list(output[, modelName := NULL])
        return(output[])
        #return(activeProcessIDTable[modelName == modelName, ])
    }
    else {
        return(activeProcessIDTable)
    }
    
}

isActiveProcess <- function(projectPath, modelName, processID, require = FALSE) {
    # Read the active process ID for the model:
    activeProcess <- getActiveProcess(
        projectPath = projectPath, 
        modelName = modelName
    ) 
    isActive <- activeProcess$processID == processID
    if(require && !isActive) {
        processName <- getProcessNameFromProcessID(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID
        )
        stop("The process ", processName, " is not the active process.")
    }
    
    return(isActive)
}

#' This function has the primary use of writing the proided activeProcessID to the activeProcessID.txt file
#' 
#' @inheritParams general_arguments
#' @param activeProcessID The process ID of the process to set as the active process. If not given, the active process ID is not changed.
#' @param affectLaterModels Logical: If TRUE reset the later models it the active pocess ID has changed in the current model.
#' @param currentActiveProcessID If both activeProcessID and currentActiveProcessID, a check is made for whether these are equal, in which case the active process ID is NOT written.
#' @param processDirty Logical: Controls whether the process has been changed (either process data or parameters) by user and not run.
#' @param propertyDirty Logical: Controls whether the properties of the process has been changed by a run, i.e. UseProcessdata automatically set to TRUE
#' 
writeActiveProcessID <- function(projectPath, modelName, activeProcessID = NULL, affectLaterModels = FALSE, currentActiveProcessID = NULL, processDirty = NULL, propertyDirty = NULL) {
    
    # Read the active process ID for the model:
    activeProcessIDTable <- getActiveProcess(projectPath, modelName = NULL)
    # Probably unelegant trick to avoid using the same name as the variable to modify:
    thisModelName <- modelName
    
    # Make sure the active processID is character prior to modification:
    if(!is.character(activeProcessIDTable$processID)) {
        activeProcessIDTable[, processID := as.character(processID)]
    }
    
    # Set the active process ID:
    if(length(activeProcessID)) {
        activeProcessIDTable[modelName == thisModelName, processID := ..activeProcessID]
    }
    # Set the processDirty status:
    if(length(processDirty)) {
        activeProcessIDTable[modelName == thisModelName, processDirty := ..processDirty]
    }
    # Set the propertyDirty status:
    if(length(propertyDirty)) {
        activeProcessIDTable[modelName == thisModelName, propertyDirty := ..propertyDirty]
    }
    
    # Set active process of all later models to NA:
    if(affectLaterModels) {
        stoxModelNames <- getRstoxFrameworkDefinitions("stoxModelNames")
        modelsToReset <- stoxModelNames[-seq_len(which(stoxModelNames == thisModelName))]
        if(length(modelsToReset) && length(activeProcessID)) {
            if(length(currentActiveProcessID)) {
                if(!identical(currentActiveProcessID, activeProcessID)) {
                    activeProcessIDTable[modelName %in% modelsToReset, processID := NA]
                    activeProcessIDTable[modelName %in% modelsToReset, processDirty := FALSE]
                    activeProcessIDTable[modelName %in% modelsToReset, propertyDirty := FALSE]
                }
            }
            else {
                activeProcessIDTable[modelName %in% modelsToReset, processID := NA]
                activeProcessIDTable[modelName %in% modelsToReset, processDirty := FALSE]
                activeProcessIDTable[modelName %in% modelsToReset, propertyDirty := FALSE]
            }
        }
    }
    
    
    # Write and return the activeProcessIDTable:
    activeProcessIDFile <- getProjectPaths(projectPath, "activeProcessIDFile")
    data.table::fwrite(activeProcessIDTable, activeProcessIDFile, sep = "\t", na = "NA")
    
    return(activeProcessIDFile)
}
writeActiveProcessIDFromTable <- function(projectPath, activeProcessIDTable) {
    # Read the active process ID for the model:
    activeProcessIDFile <- getProjectPaths(projectPath, "activeProcessIDFile")
    if(!file.exists(activeProcessIDFile)) {
        warning("StoX: The active process ID file has not been initiated.")
    }
    data.table::fwrite(activeProcessIDTable, activeProcessIDFile, sep = "\t", na = "NA")
}


#' Reset a StoX model.
#' 
#' @inheritParams general_arguments
#' @inheritParams unReDoProject
#' @param processDirty Logical: Indicates whether the model has been modified when resetting. Tf the process to reset to is after the active process, 
#' @param delete A character vector naming which elements to delete, where possible values are "memory", for deleting the output files that are stored as memory files, and "text" to delete the output text files.
#' @param deleteCurrent Logical: If TRUE delete process output also of the process given by processID.
#' @param purgeOutputFiles Logical: If the model has not been run, should the output text files be deleted first. This was used at an earlier stage, when there was not complete control of how process output was deleted, and data were frequently not deleted eevn though the process was deleted.
#' 
#' @export
#'
resetModel <- function(projectPath, modelName, processID = NULL, processDirty = FALSE, shift = 0, returnProcessTable = FALSE, delete = c("memory", "text"), deleteCurrent = FALSE, purgeOutputFiles = FALSE) {
    
    # Get the process ID to reset the model to:
    processIndexTable <- readProcessIndexTable(projectPath, modelName)
    
    # Get the processIndex, that is the index of the process to reset to:
    if(length(processID) == 0 || is.na(processID)) {
        processIndex <- 0
    }
    else {
        processIndex <- which(processIndexTable$processID == processID) + shift
        # Error if the process does not exist
        if(length(processIndex) == 0) {
            stop("StoX: processID not regocnized")
        }
    }
    
    # Read the active proces ID and reset if that is not NA:
    currentActiveProcessID <- getActiveProcess(projectPath = projectPath, modelName = modelName)$processID
    
    # If activeprocessID is NA, do nothing, as this indicates that the model has not been run:
    if(!is.na(currentActiveProcessID)) {
        
        ##### (1) Set active process: #####
        # Get the current active process index:
        currentActiveProcessIndex <- which(processIndexTable$processID == currentActiveProcessID)
        
        # If the processIndex is 0 or processID not given, reset to the start of the model (activeProcessID = NA):
        if(processIndex == 0 || length(processID) == 0) {
            newActiveProcessID <- NA
        }
        else {
            # Reset only if the input process ID is before that of the active:
            if(processIndex <= currentActiveProcessIndex) {
                    newActiveProcessID <- processIndexTable$processID[processIndex]
                }
            else {
                #newActiveProcessID <- currentActiveProcessID
                # Return a list of the active process and the process table:
                output <- list(
                    activeProcess = getActiveProcess(projectPath = projectPath, modelName = modelName)
                )
                if(returnProcessTable) {
                    output <- c(
                        list(processTable = getProcessTable(projectPath = projectPath, modelName = modelName)), 
                        output
                    )
                }
                
                return(output)
            }
        }
        
        # Write the active process ID:
        #if(is.na(newActiveProcessID) || newActiveProcessID != currentActiveProcessID) {
        writeActiveProcessID(projectPath = projectPath, modelName = modelName, activeProcessID = newActiveProcessID, affectLaterModels = TRUE, currentActiveProcessID = currentActiveProcessID, processDirty = processDirty)
        #}
        
        ##### (2) Delete process output of the processes from the new active process and onwards: #####
        if(deleteCurrent) {
            processIndex <- processIndex - 1
        }
        if(currentActiveProcessIndex > processIndex) {
            # Get all processes from the process to reset to and onwards:
            allProcessIndex <- getProcessIndexFromProcessID(
                projectPath = projectPath, 
                modelName = modelName, 
                processIndexTable$processID
            )
            
            if("memory" %in% delete) {
                IDsOfProcessesToDelete <- processIndexTable$processID[allProcessIndex > processIndex]
                foldersToDelete <- sapply(
                    IDsOfProcessesToDelete, 
                    function(thisProcessID) getProcessOutputFolder(
                        projectPath = projectPath, 
                        modelName = modelName, 
                        processID = thisProcessID, 
                        type = "memory"
                    )
                )
                unlink(foldersToDelete, recursive = TRUE, force = TRUE)
            }
            
            
            ##### (3) Delete process output text files: #####
            
            #### Get the list of output text files:
            ###folderPath <- getProjectPaths(projectPath = projectPath, name = modelName)
            ###outputTextFiles <- list.files(folderPath, full.names = TRUE)
            ###
            #### Identify the output file with prefix later than the index of the process to reset to:
            ###prefix <- as.numeric(sub("\\_.*", "", basename(outputTextFiles)))
            ###filesToDelete <- outputTextFiles[prefix > processIndex]
            ###
            #### Delete the files:
            ###unlink(filesToDelete, recursive = TRUE, force = TRUE)
            if("text" %in% delete) {
                namesOfProcessesToDelete <- processIndexTable$processName[allProcessIndex > processIndex]
                foldersToDelete <- sapply(
                    IDsOfProcessesToDelete, 
                    function(thisProcessID) getProcessOutputFolder(
                        projectPath = projectPath, 
                        modelName = modelName, 
                        processID = thisProcessID, 
                        type = "text"
                    )
                )
                unlink(foldersToDelete, recursive = TRUE, force = TRUE)
            }
        }
    }
    else if(purgeOutputFiles) {
        foldersToDelete <- sapply(
            processIndexTable$processID, 
            function(thisProcessID) getProcessOutputFolder(
                projectPath = projectPath, 
                modelName = modelName, 
                processID = thisProcessID, 
                type = "text"
            )
        )
        unlink(foldersToDelete, recursive = TRUE, force = TRUE)
    }
    
    # Return a list of the active process and the process table:
    output <- list(
        activeProcess = getActiveProcess(projectPath = projectPath, modelName = modelName)
    )
    if(returnProcessTable) {
        output <- c(
            list(processTable = getProcessTable(projectPath = projectPath, modelName = modelName)), 
            output
        )
    }
   
    #output <- list(
    #    if(returnProcessTable) processTable = getProcessTable(projectPath = projectPath, modelName = modelName), 
    #    activeProcess = getActiveProcess(projectPath = projectPath, modelName = modelName)
    #)
    return(output)
}





# (1a) argumentFile:
# Every process argument (one of processName, functionName, processParameters, processData, functionInputs and functionParameters) is written as an argumentFile.
#
# (1b) argumentValue:
# The data stored in an argumentFile.
#
# (2) projectMemory:
# A vector of the file paths of the argumentFile comprising the projectMemory
#
# (3) projectMemoryFile:
# The file holding the argumentFileList. There is one argumentFileListFile for each change, which can involve multiple individal changes.
#
# (4) projectMemoryFilePath:
# The path to the file holding the argumentFileList. There are two special files named originalArgumentFileList and currentArgumentFileList. 
# 
# (5) originalProjectMemory:
# The file holding the original projectMemory as a list of files.
# 
# (5) currentProjectMemory:
# The file holding the current projectMemory as a list of files.

# (6) projectMemoryData:
# A nested list of the individual argument values with levels modelName, processID, argumentName and argumentValue




# 5. Function to read the current project memory, or parts of it (e.g., argumentName = NULL indicate all arguments of the specified process(es)):


# Unused.
appendProjectDescription <- function(projectDescription, modelName, processID, argumentName, argumentValue) {
    # Append the missing list elements down to the argument:
    if(!modelName %in% names(projectDescription)) {
        projectDescription <- append(
            projectDescription, 
            structure(list(NULL), names = modelName)
        )
    }
    if(!processID %in% names(projectDescription [[modelName]])) {
        projectDescription [[modelName]] <- append(
            projectDescription[[modelName]], 
            structure(list(NULL), names = processID)
        )
    }
    # If missing, append the argument, and if not replace it:
    if(!argumentName %in% names(projectDescription [[modelName]] [[processID]])) {
        projectDescription [[modelName]] [[processID]] <- append(
            projectDescription [[modelName]] [[processID]], 
            structure(list(argumentValue), names = argumentName)
        )
    }
    else {
        projectDescription [[modelName]] [[processID]] [[argumentName]] <- argumentValue
    }
    
    return(projectDescription)
}


# Function for getting the file path of one specific process argument file.
getNewArgumentFileSansExt <- function(projectPath, modelName, processID, argumentName) {
    
    # Get the folder holding the project descriptions:
    memoryModelsFolder <- getProjectPaths(projectPath, "memoryModelsFolder")
    argumentFolder <- file.path(memoryModelsFolder, modelName, processID, argumentName)
    
    addTimeToFileName(
        fileName = argumentName, 
        dir = argumentFolder
    )
    
    ## Define a string with time in ISO 8601 format:
    #timeString <- format(Sys.time(), tz = "UTC", format = "%Y%m%dT%H%M%OS3Z")
    ## Define the file name including the time string, and build the path to the file:
    ##fileName <- paste0(argumentName, "_", timeString, ".rds")
    #fileName <- paste0(argumentName, "_", timeString)
    #filePath <- file.path(argumentFolder, fileName)
    #filePath
}


# Function for getting the file path of a new project memory file.
getNewProjectMemoryFileSansExt <- function(projectPath) {
    # Get the folder holding the project descriptions:
    memoryFolder <- getProjectPaths(projectPath, "memoryHistoryFolder")
    
    addTimeToFileName(
        fileName = "projectMemory", 
        dir = memoryFolder
    )
    #
    ## Define a string with time in ISO 8601 format:
    #timeString <- format(Sys.time(), tz = "UTC", format = "%Y%m%dT%H%M%OS3Z")
    ## Define the file name including the time string, and build the path to the file:
    ##fileName <- paste0("projectMemory", "_", timeString, ".rds")
    #fileName <- paste0("projectMemory", "_", timeString)
    #filePath <- file.path(memoryFolder, fileName)
    #filePath
}




addTimeToFileName <- function(fileName, dir) {
    # Define a string with time in ISO 8601 format:
    StoxTimeZone <- RstoxData::getRstoxDataDefinitions("StoxTimeZone")
    
    timeString <- format(Sys.time(), tz = StoxTimeZone, format = "%Y%m%dT%H%M%OS3Z")
    # Define the file name including the time string, and build the path to the file:
    fileName <- paste0(fileName, "_", timeString)
    filePath <- file.path(dir, fileName)
    
    return(filePath)
}


# Function for saving an argument value to one process argument file:
saveArgumentFile <- function(projectPath, modelName, processID, argumentName, argumentValue, ext = "rds") {
    
    # Get the path to the new argument file:
    argumentFileSansExt <- getNewArgumentFileSansExt(projectPath = projectPath, modelName = modelName, processID = processID, argumentName = argumentName)
    
    # Save the argument to the file, and return the file path:
    argumentFilePath <- writeMemoryFile(
        argumentValue, 
        filePathSansExt = argumentFileSansExt, 
        ext = ext
    )
    
    # Return the file path relative to the project path:
    #relativePath <- sub(projectPath, "", argumentFilePath)
    relativePath <- getRelativePath(
        filePath = argumentFilePath, 
        projectPath = projectPath
    )
    
    return(relativePath)
}





# Unused.
insertToArgumentFileTable <- function(argumentFileTable, modelName, processID, argumentName, argumentFile) {
    
    # Function to get the row index of a combination of values of the columns of argumentFileTable:
    getRowIndex <- function(ind, argumentFilesToInsert, argumentFileTable) {
        # Get the indices at which to insert the row of argumentFilesToInsert:
        atModelName    <- argumentFilesToInsert$modelName[ind]    == argumentFileTable$modelName
        atProcessID    <- argumentFilesToInsert$processID[ind]    == argumentFileTable$processID
        atArgumentName <- argumentFilesToInsert$argumentName[ind] == argumentFileTable$argumentName
        index <- which(atModelName & atProcessID & atArgumentName)
        # Return NA for missing indices:
        if(length(index) == 0) {
            index <- NA
        }
        index
    }
    
    # Define a data.table of the same form as the argumentFileTable, with the data to insert:
    argumentFilesToInsert <- data.table::data.table(
        modelName = modelName, 
        processID = processID, 
        argumentName = argumentName, 
        argumentFile = argumentFile
    )
    
    # Identify the row which are not present in the current argumentFileTable:
    index <- sapply(
        seq_len(nrow(argumentFilesToInsert)), 
        getRowIndex, 
        argumentFilesToInsert = argumentFilesToInsert, 
        argumentFileTable = argumentFileTable
    )
    
    # Detect the new files:
    additions <- is.na(index)
    
    # Replace the argument files:
    argumentFileTable[index[!additions], "argumentFile"] <- argumentFilesToInsert[!additions, "argumentFile"]
    
    # Append the new argument files:
    argumentFileTable <- rbind(
        argumentFileTable, 
        argumentFilesToInsert[additions, ], 
        fill = TRUE
    )
    
    # Return the modified table:
    argumentFileTable
}

# Unused.
removeFromArgumentFileTable <- function(argumentFileTable, modelName, processID) {
    
    # Get the rows in the argument file table to remove, which are those with processID as that specified by the user to remove:
    toRemove <- argumentFileTable$processID == processID & argumentFileTable$modelName == modelName
    
    # Remove the argument files of the process:
    if(any(toRemove)) {
        argumentFileTable <- argumentFileTable[!toRemove, ]
    }
    else {
        warning("StoX: The process with processID ", processID, " was not found in the current state of the model")
    }
    
    # Return the argument file table:
    argumentFileTable
}


deleteProjectMemoryFile <- function(projectPath, projectMemoryFileRelativePath) {
    unlink(file.path(projectPath, projectMemoryFileRelativePath))
}








# Split a projectMemoryData object to a list of the elements modelName, processID, argumentName, argumentValue.
splitProjectMemoryList <- function(projectMemoryData) {
    
    # Unlist the projectMemory twice to reach the process argument level, then extract the names splitting by dot (as unlist concatenates the names with dot as separator):
    projectMemoryData <- unlist(unlist(projectMemoryData, recursive = FALSE), recursive = FALSE)
    model_process_argument <- names(projectMemoryData)
    model_process_argument <- strsplit(model_process_argument, ".", fixed = TRUE)
    # Extract the modelName, processID and argumentName
    modelName <- sapply(model_process_argument, "[[", 1)
    processID <- sapply(model_process_argument, "[[", 2)
    argumentName <- sapply(model_process_argument, "[[", 3)
    
    # Create a data.table with the modelName, processID, argumentName and argumentValue (the latter may be a list):
    data.table::data.table(
        modelName = modelName, 
        processID = processID, 
        argumentName = argumentName, 
        argumentValue = unname(projectMemoryData)
    )
}



# Function to read the projectDescriptionIndexFile:
readProjectMemoryIndex <- function(projectPath) {
    # Read the projectMemoryIndexFile:
    projectMemoryIndexFile <- getProjectPaths(projectPath, "projectMemoryIndexFile")
    
    # If missing, create the file as an empty file:
    if(!file.exists(projectMemoryIndexFile)) {
        NULL
    }
    else {
        data.table::fread(projectMemoryIndexFile, sep = "\t", encoding = "UTF-8")
    }
}

# Function to write the projectDescriptionIndexFile:
writeProjectMemoryIndex <- function(projectPath, projectMemoryIndex) {
    # Read the projectDescriptionIndexFile:
    projectMemoryIndexFile <- getProjectPaths(projectPath, "projectMemoryIndexFile")
    data.table::fwrite(projectMemoryIndex, file =  projectMemoryIndexFile, sep = "\t")
}



#' Function to undo or redo, i.e., reset the current project description file and change the indices. There will be separate GUI functions for undo and redo:
#' 
#' @inheritParams general_arguments
#' @param shift The position relative to the current memory status to un/redo to.
#' 
#' @export
#' 
unReDoProject <- function(projectPath, shift = 0) {
    # Read the projectDescriptionIndexFile, and add the shift value to the index:
    projectMemoryIndex <- readProjectMemoryIndex(projectPath)
    projectMemoryIndex$Index <- projectMemoryIndex$Index - shift
    writeProjectMemoryIndex(projectPath, projectMemoryIndex)
    
    # Copy the projectMemory with index = 0 to the currentProjectMemoryFile:
    fileWithNewCurrentProjectMemory  <- file.path(
        projectPath, 
        projectMemoryIndex$Path[projectMemoryIndex$Index == 0]
    )
    #file.copy(
    #    from = fileWithNewCurrentProjectMemory, 
    #    to = getProjectPaths(projectPath, "currentProjectMemoryFile"), 
    #    overwrite = TRUE, 
    #    copy.date = TRUE
    #)
    
    # Rewrite the text file holding processIndexTable, activeProcessIDTable and maxProcessIntegerID:
    unwrapProjectMemoryFile(fileWithNewCurrentProjectMemory)
}

# Function to unwrap a project memory history file to multiple individual files
unwrapProjectMemoryFile <- function(projectMemoryFile) {
    # Read the project memory to get the data to write to the text files:
    #projectMemory <- readRDS(projectMemoryFile)
    projectMemory <- readMemoryFile(
        projectMemoryFile
    )
    
    # Unwrap and overwrite the process index table file:
    writeProcessIndexTable(projectPath, projectMemory$processIndexTable)
    
    # Unwrap and overwrite the active process ID file:
    writeActiveProcessIDFromTable(projectPath, projectMemory$activeProcessIDTable)
    
    # Unwrap and overwrite the maximum process integer ID file:
    writeMaxProcessIntegerID(projectPath, projectMemory$maxProcessIntegerID)
    
    stop("StoX: Here we need to code replacing the memory files!!!!!!!!!!!!!")
}






##################################
##### StoX function library: #####
##################################

#getStoxFunctionAttributes <- function(packageName) {
#    package <- paste0("package", packageName)
#    get("stoxFunctionAttributes", pos = package)
#}

#' Function returning the names of the StoX functions available for a model:
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
getAvailableStoxFunctionNames <- function(modelName) {
    
    # Get the function meta data:
    stoxLibrary <- getRstoxFrameworkDefinitions("stoxLibrary")
    
    # Get the names of the available functions:
    availableFunctions <- names(stoxLibrary)
    # Get the category of each function, and split by category:
    functionCategories <- sapply(stoxLibrary, "[[", "functionCategory")
    availableFunctionsByCategory <-split(availableFunctions, functionCategories)
    # Sort each category:
    availableFunctionsByCategory <- lapply(availableFunctionsByCategory, sort)
    
    # Keep only the valid category:
    availableFunctionsByCategory[modelName]
}

# Function for getting specific metadata of a function, or all metadata if metaDataName = NULL:
getStoxFunctionMetaData <- function(functionName, metaDataName = NULL, showWarnings = TRUE) {
    
    # If empty function name, return empty list:
    if(length(functionName) == 0 || nchar(functionName) == 0) {
        if(length(metaDataName) == 0) {
            return(list())
        }
        else {
            return(NULL)
        }
    }
    
    # Get the function name (without package name ::):
    packageName <- getPackageNameFromFunctionName(functionName)
    functionName <- getFunctionNameFromPackageFunctionName(functionName)
    
    # Get the function meta data:
    stoxLibrary <- getRstoxFrameworkDefinitions("stoxLibrary")
    # Match the metaDataName with the available meta data and return:
    if(length(metaDataName) == 0) {
        stoxLibrary [[functionName]]
    }
    else if(metaDataName %in% names(stoxLibrary[[functionName]])) {
        stoxLibrary [[functionName]] [[metaDataName]]
    }
    else if(!length(stoxLibrary[[functionName]])) {
        if(showWarnings) {
            warning("StoX: The function ", functionName, "is not present. Please install ", sub("\\:.*", "", packageName), ".")
        }
    }
    else {
        if(showWarnings) {
            warning("StoX: The requested meta data ", metaDataName, " is not included in the stoxFunctionAttributes for function ", functionName, ".")
        }
        NULL
    }
}

# Function to return the names of the arguments to show for a function:
getArgumentsToShow <- function(projectPath, modelName, processID, argumentFilePaths = NULL, return.only.names = TRUE) {
    
    # Get the function name and arguments:
    functionName <- getFunctionName(projectPath = projectPath, modelName = modelName, processID = processID, argumentFilePaths = argumentFilePaths)
    # Get the function argument hierarchy:
    functionArgumentHierarchy <- getStoxFunctionMetaData(functionName, "functionArgumentHierarchy", showWarnings = FALSE)
    
    # Get the function inputs and the function parameters:
    functionInputs <- getFunctionInputs(projectPath = projectPath, modelName = modelName, processID = processID, argumentFilePaths = argumentFilePaths)
    functionParameters <- getFunctionParameters(projectPath = projectPath, modelName = modelName, processID = processID, argumentFilePaths = argumentFilePaths)
    
    if(length(functionArgumentHierarchy) && any(rapply(functionArgumentHierarchy, is.function))) {
        
        # Get the actual function input data, as it is needed when functionArgumentHierarchy is a function:
        functionInputs <- getFunctionInputData(
            functionInputProcessNames = functionInputs, 
            projectPath = projectPath, 
            # Do not stop if the input is not specified, as we may not yet have defined the parameters:
            strict = FALSE
        )
        
    }
    functionArguments <- c(functionInputs, functionParameters)
    
    
    RstoxData::applyFunctionArgumentHierarchy(
        functionArgumentHierarchy = functionArgumentHierarchy, 
        functionArguments = functionArguments, 
        return.only.names = return.only.names
    )
}

# Function to extract the actual the arguments to show from the arguments:
extractArgumentsToShow <- function(arguments, projectPath, modelName, processID, argumentFilePaths = NULL, keepSystemParameters = TRUE) {
    # Get the names of the arguments to show:
    argumentsToShow <- getArgumentsToShow(projectPath = projectPath, modelName = modelName, processID = processID, argumentFilePaths = argumentFilePaths)
    # extract only the variables to show:
    #arguments$functionInputs <- arguments$functionInputs[intersect(names(arguments$functionInputs), argumentsToShow)]
    #arguments$functionParameters <- arguments$functionParameters[intersect(names(arguments$functionParameters), argumentsToShow)]
    systemParameters <- getRstoxFrameworkDefinitions("systemParameters")
    if(keepSystemParameters) {
        argumentsToShow <- c(systemParameters, argumentsToShow) 
    }
    argumentsToShow <- intersect(names(arguments), argumentsToShow)
    
    arguments <- arguments[argumentsToShow]
    
    return(arguments)
}

# Is the function a process data function?
isProcessDataFunction <- function(functionName) {
    # Get the function output data type and match against the defined process data types:
    #functionOutputDataType <- getStoxFunctionMetaData(functionName, "functionOutputDataType")
    #functionOutputDataType %in% getRstoxFrameworkDefinitions("stoxProcessDataTypes")
    identical(getStoxFunctionMetaData(functionName, "functionType"), "processData")
}

# Is the function a bootstrap function?
isBootstrapFunction <- function(functionName) {
    # Get the function output data type and match against the defined process data types:
    #functionOutputDataType <- getStoxFunctionMetaData(functionName, "functionOutputDataType")
    #functionOutputDataType %in% getRstoxFrameworkDefinitions("stoxProcessDataTypes")
    getStoxFunctionMetaData(functionName, "functionType") %in% c("bootstrap")
}
# Is the function a bootstrap function?
isBootstrapNetCDF4Function <- function(functionName) {
    # Get the function output data type and match against the defined process data types:
    getStoxFunctionMetaData(functionName, "functionType") %in% "bootstrapNetCDF4"
}



# Function which gets the values defined for the parameters in the definition of a function:
getStoxFunctionParameterPossibleValues <- function(functionName, fill.logical = TRUE) {
    
    # Get all defaults:
    output <- getStoxFunctionParameterFormals(functionName)
    
    # Get the parameter (primitive) type to enable the treatments of logicals and numerics:
    parameterType <- unlist(getStoxFunctionParameterTypes(functionName))
    
    # Insert c(FALSE, TRUE) for logicals:
    if(fill.logical) {
        #areLogicals <- sapply(output, is.logical)
        areLogicals <- parameterType %in% "logical"
        if(any(areLogicals)) {
            output[areLogicals] <- lapply(output[areLogicals], expandLogical)
        }
    }
    
    # Remove possible values for numeric. Any restrictions of numerics should rather reside in the function definition, resulting in warnings or errors:
    areNumeric <- parameterType %in% c("numeric", "integer", "double")
    if(any(areNumeric)) {
        output[areNumeric] <- vector("list", sum(areNumeric))
    }
    
    
    return(output)
}



# Function which gets the default values of a function:
getStoxFunctionParameterFormals <- function(functionName) {
    # Get the available functions:
    availableFunctions <- getRstoxFrameworkDefinitions("availableFunctions")
    
    functionName <- getFunctionNameFromPackageFunctionName(functionName)
    if(! functionName %in% availableFunctions) {
        warning("StoX: The function ", functionName, " is not an official StoX function.")
        return(list())
    }
    
    # Get all possible values:
    possibleValues <- getRstoxFrameworkDefinitions("availableFunctionPossibleValues")[[functionName]]
    return(possibleValues)
}


# Function which gets the default values of a function:
getStoxFunctionParameterDefaults <- function(functionName) {
    # Get the possible values of the parameters of a function:
    StoxFunctionParameterFormals <- getStoxFunctionParameterFormals(functionName)
    
    # Keep only logicals:
    setTo0Length <- !lapply(StoxFunctionParameterFormals, getRelevantClass) %in% "logical" & lengths(StoxFunctionParameterFormals) > 0
    StoxFunctionParameterFormals[setTo0Length] <- lapply(sapply(StoxFunctionParameterFormals[setTo0Length],  RstoxData::firstClass), do.call, list(0))
    
    return(StoxFunctionParameterFormals)
}

# Function which gets the primitive types of the parameters of a function:
getStoxFunctionParameterPrimitiveTypes <- function(functionName) {
    # Get the possible values of the parameters of a function:
    functionParameterDefault <- getStoxFunctionParameterFormals(functionName)
    # The default is the first value:
    primitiveType <- lapply(functionParameterDefault, getRelevantClass)
    return(primitiveType)
}
# Function which gets the primitive types of the parameters of a function:
getStoxFunctionParameterTypes <- function(functionName) {
    
    # Get the primitive types of the parameters of a function (as specified in the function definition):
    typeFromDefinition <- getStoxFunctionParameterPrimitiveTypes(functionName)
    
    # Removed on 2020-08-13, since all parameters should have default value reflecting the primitive type (character() instead of NULL, etc.)
    ### # Get the meta data functionParameterType (as specified in the 'stoxFunctionAttributes' of each package):
    ### functionParameterType <- getStoxFunctionMetaData(functionName, "functionParameterType")
    ### 
    ### # Replace the types by those from the meta data:
    ### valid <- intersect(names(typeFromDefinition), names(functionParameterType))
    ### if(length(valid)) {
    ###     typeFromDefinition[valid] <- functionParameterType[valid]
    ### }
    
    # If not integer, double or logical, set to character (as all other types than these are wrapped to JSON strings by the GUI):
    processPropertyTypes <- getRstoxFrameworkDefinitions("processPropertyTypes")
    setAsCharacter <- !typeFromDefinition %in% processPropertyTypes$optional
    typeFromDefinition[setAsCharacter] <- processPropertyTypes$default
    
    # Return the types:
    return(typeFromDefinition)
}

# Function which applies the default format on formats not recognized :
getFunctionParameterFormats <- function(functionName) {
    
    # Get the types, and interpret all types as format "none":
    formats <- getStoxFunctionParameterTypes(functionName)
    formats[] <- "none"
    
    # Get the meta data functionParameterFormat (as specified in the 'stoxFunctionAttributes' of each package):
    functionParameterFormat = getStoxFunctionMetaData(functionName, "functionParameterFormat")
    
    # Replace the formats by those from the meta data:
    valid <- intersect(names(formats), names(functionParameterFormat))
    if(length(valid)) {
        formats[valid] <- functionParameterFormat[valid]
    }
    
    # Return the formats:
    return(formats)
}









#############################################################
##### Functions for extracting properties of processes: #####
#############################################################
getFunctionName <- function(projectPath, modelName, processID, argumentFilePaths = NULL) {
    getProjectMemoryData(
        projectPath, 
        modelName = modelName, 
        processID = processID, 
        argumentName = "functionName", 
        drop1 = TRUE, 
        argumentFilePaths = argumentFilePaths
    )
}

getFunctionInputs <- function(projectPath, modelName, processID, only.valid = FALSE, argumentFilePaths = NULL) {
    functionInputs <- getProjectMemoryData(
        projectPath, 
        modelName = modelName, 
        processID = processID, 
        argumentName = "functionInputs", 
        drop1 = TRUE, 
        argumentFilePaths = argumentFilePaths
    )
    
    if(only.valid) {
        argumentsToShow <- getArgumentsToShow(projectPath, modelName = modelName, processID = processID, argumentFilePaths = argumentFilePaths)
        functionInputs <- functionInputs[intersect(names(functionInputs), argumentsToShow)]
    }
    
    return(functionInputs)
}

getFunctionParameters <- function(projectPath, modelName, processID, only.valid = FALSE, argumentFilePaths = NULL) {
    functionParameters <- getProjectMemoryData(
        projectPath, 
        modelName = modelName, 
        processID = processID, 
        argumentName = "functionParameters", 
        drop1 = TRUE, 
        argumentFilePaths = argumentFilePaths
    )
    
    if(only.valid) {
        argumentsToShow <- getArgumentsToShow(projectPath, modelName = modelName, processID = processID, argumentFilePaths = argumentFilePaths)
        functionParameters <- functionParameters[intersect(names(functionParameters), argumentsToShow)]
    }
    
    return(functionParameters)
}

getProcessName <- function(projectPath, modelName, processID, argumentFilePaths = NULL) {
    getProjectMemoryData(
        projectPath, 
        modelName = modelName, 
        processID = processID, 
        argumentName = "processName", 
        drop1 = TRUE, 
        argumentFilePaths = argumentFilePaths
    )
}

getProcessParameters <- function(projectPath, modelName, processID, argumentFilePaths = NULL) {
    getProjectMemoryData(
        projectPath, 
        modelName = modelName, 
        processID = processID, 
        argumentName = "processParameters", 
        drop1 = TRUE, 
        argumentFilePaths = argumentFilePaths
    )
}

# This function gets the process data as stored in the process memory files. These process data may differ from the process data output from the process, stored in the output data files, particularly if interactive functions have been used. In this case, the process must be run again with UseProcessData = TRUE (automatically set by RstoxFramework) to update the process output, which is used in runProcess() using getProcessOutput(). 
getProcessData <- function(projectPath, modelName, processID, argumentFilePaths = NULL, check.activeProcess = FALSE) {
    
    # Check that the process is the active process:
    if(check.activeProcess) {
        isActiveProcess(projectPath = projectPath, modelName = modelName, processID = processID, require = TRUE)
    }
    
    # Get and return the processData:
    getProjectMemoryData(
        projectPath, 
        modelName = modelName, 
        processID = processID, 
        argumentName = "processData", 
        drop1 = TRUE, 
        argumentFilePaths = argumentFilePaths
    )
}

getProcessArguments <- function(projectPath, modelName, processID, argumentFilePaths = NULL, only.valid = FALSE) {
    # Read the memory of the process:
    process <- getProjectMemoryData(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        drop1 = TRUE, 
        argumentFilePaths = argumentFilePaths
    )
    
    # Add the processID:
    process$processID <- processID
    
    # Add the output data file path(s):
    #warning("StoX: Add the output data file path(s)__________________________")
    
    if(only.valid) {
        #argumentsToShow <- getArgumentsToShow(projectPath = projectPath, modelName = modelName, processID = processID, argument#FilePaths = argumentFilePaths)
        #process$functionInputs <- process$functionInputs[intersect(names(process$functionInputs), argumentsToShow)]
        #process$functionParameters <- process$functionParameters[intersect(names(process$functionParameters), argumentsToShow)]
        process <- extractArgumentsToShow(arguments = process, projectPath = projectPath, modelName = modelName, processID = processID, argumentFilePaths = argumentFilePaths)
    }
    
    return(process)
}


#' Get the properties of a StoX process
#' 
#' This function lists the process name, the function name, the process parameters, the function parameters, the function inputs and the process data of a StoX process.
#' 
#' @inheritParams general_arguments
#' @param only.valid Logical: If TRUE return only the valid arguments, which are those visible in the GUI. 
#' 
#' @export
#' 
getProcess <- function(projectPath, modelName, processName, only.valid = FALSE) {
    
    processID <- getProcessIDFromProcessName(
        projectPath = projectPath, 
        modelName = modelName, 
        processName = processName, 
        only.processID = TRUE
    )
        
    getProcessArguments(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        only.valid = only.valid
    )
}

getDataType <- function(projectPath, modelName, processID, argumentFilePaths = NULL) {
    # Get the function name:
    functionName <- getFunctionName(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        argumentFilePaths = argumentFilePaths
    )
    # Get the data type from the function name:
    functionOutputDataType <- getStoxFunctionMetaData(functionName, "functionOutputDataType")
    if(length(functionOutputDataType) == 0) {
        functionOutputDataType <- ""
    }
    
    return(functionOutputDataType)
}

checkDataType <- function(dataType, projectPath, modelName, processID) {
    #dataType %in% getDataType(projectPath, modelName, processID)
        if(!dataType %in% getDataType(projectPath = projectPath, modelName = modelName, processID = processID)) {
        stop("StoX: The process ", getProcessName(projectPath = projectPath, modelName = modelName, processID = processID), " does not return ", dataType, " data.")
    }
}
    

##### Functions for manipulating the process index table, which defines the order of the processes. These functions are used by the frontend to delete, add, and reorder processes: #####
readProcessIndexTable <- function(projectPath, modelName = NULL, processes = NULL, startProcess = 1, endProcess = Inf, warn = TRUE, return.processIndex = FALSE) {
    
    # Get the path to the process index file:
    processIndexTableFile <- getProjectPaths(projectPath, "processIndexTableFile")
    
    # If missing, create the file as an empty file:
    if(!file.exists(processIndexTableFile)) {
        return(data.table::data.table())
    }
    
    # Otherwise read the table from the file:
    
    # Read and extract the specified model:
    processIndexTable <- data.table::fread(processIndexTableFile, sep = "\t", encoding = "UTF-8")
    # Add process Indices for each model:
    processIndexTable[, processIndex := seq_along(processID), by = "modelName"]
    
    # Return immediately if modelName is empty (returning the entire table):
    if(length(modelName) == 0) {
        if(!return.processIndex) {
            processIndexTable[, processIndex := NULL]
        }
        return(processIndexTable)
    }
    
    # Extract the model:
    #if(! modelName %in% processIndexTable$modelName) {
    #    stop("The modelName \"", modelName, "\" is not the name of an existing model (a model with one or more processes). Poss#ible values are ", paste(names(sort(unique(processIndexTable$modelName)))))
    #}
    validRows <- processIndexTable$modelName %in% modelName
    processIndexTable <- subset(processIndexTable, validRows)
    
    # If the model in empty, return an empty data.table:
    if(nrow(processIndexTable) == 0) {
        return(data.table::data.table())
    }
    
    # If present, interpret the requested 'processes' input:
    if(length(processes)) {
        if(identical(processes, NA)) {
            return(processIndexTable[FALSE, ])
        }
        processesNumeric <- matchProcesses(
            processes, 
            processIndexTable, 
            warn = warn
        )
    }
    else {
        # startProcess and endProcess can be given as process names or IDs:
        startProcessNumeric <- matchProcesses(startProcess, processIndexTable, warn = warn)
        endProcessNumeric <- matchProcesses(endProcess, processIndexTable, warn = warn)
        # If there are less elements affter matching, issue a warning:
        if(any(is.na(startProcessNumeric))) {
            warning(
                "The following processes are not present in the project.", 
                paste0(startProcess[is.na(startProcessNumeric)], collapse = ", ")
            )
            startProcessNumeric <- startProcessNumeric[!is.na(startProcessNumeric)]
        }
        if(any(is.na(endProcessNumeric))) {
            warning(
                "The following processes are not present in the project.", 
                paste0(endProcess[is.na(endProcessNumeric)], collapse = ", ")
            )
            endProcessNumeric <- endProcessNumeric[!is.na(endProcessNumeric)]
        }
        
        if(!length(startProcessNumeric) && !length(endProcessNumeric)) {
            stop("StoX: At least one of startProcess and endProcess must specify valid processes")
        }
        else if(!length(startProcessNumeric)) {
            warning("StoX: The startProcess was not found, and was set to the last valid endProcess")
            startProcessNumeric <- endProcessNumeric
        }
        else if(!length(endProcessNumeric)) {
            warning("StoX: The endProcess was not found, and was set to the first valid startProcess")
            endProcessNumeric <- startProcessNumeric
        }
        
        # Allow for a vector of start processes, in which case the earliest is selected (and the latest end process):
        startProcessNumeric <- min(startProcessNumeric)
        endProcessNumeric <- max(endProcessNumeric)
        
        # Restrict the startProcess and endProcess to the range of process indices:
        startProcessNumeric <- max(1, startProcessNumeric)
        endProcessNumeric <- min(nrow(processIndexTable), endProcessNumeric)
        
        processesNumeric <- seq(startProcessNumeric, endProcessNumeric)
    }
    
    # Extract the requested process IDs:
    processIndexTable <- processIndexTable[processesNumeric, ]
    
    if(!return.processIndex) {
        processIndexTable[, processIndex := NULL]
    }
    
    return(processIndexTable)
}

matchProcesses <- function(processes, processIndexTable, warn = TRUE) {
    if(is.numeric(processes)) {
        processesNumeric <- processes
    }
    else if(is.character(processes)) {
        # Match for process names first:
        processesNumeric <- match(processes, processIndexTable$processName)
        # Then match for process IDs: 
        unassigned <- is.na(processesNumeric)
        processesNumeric[unassigned] <- match(processes[unassigned], processIndexTable$processID)
        # Strip of the processes that were not recognised:
        if(any(is.na(processesNumeric))) {
            # stop("StoX: The following processes were not recognized as process names or process IDs: ", paste(processes[is.na(processesNumeric)], collapse = ", "), ".")
            if(warn) {
                stop("StoX: The following processes were not recognized as process names or process IDs: ", paste(processes[is.na(processesNumeric)], collapse = ", "), ".")
            }
            processesNumeric <- processesNumeric[!is.na(processesNumeric)]
        }
    }
    else {
        stop("StoX: Processes must be specified as a vector of process indices, names or IDs (possibly a mixture of the latter two.)")
    }
    
    return(processesNumeric)
}


writeProcessIndexTable <- function(projectPath, processIndexTable) {
    # Get the path to the process index file:
    processIndexTableFile <- getProjectPaths(projectPath, "processIndexTableFile")
    # write the file:
    #processIndexTable <- data.table::fwrite(processIndexTable[, c("processID", "processName", "modelName")], processIndexTableFile, sep = "\t")
    data.table::fwrite(processIndexTable, processIndexTableFile, sep = "\t")
}


addToProcessIndexTable <- function(projectPath, modelName, processID, processName, afterIndex = NULL) {
    
    # Get the process index file:
    #processIndexTable <- readProcessIndexTable(projectPath = projectPath, modelName = modelName)
    processIndexTable <- readProcessIndexTable(projectPath = projectPath)
    
    # Get the default 'afterIndex':
    atModel <- processIndexTable$modelName == modelName
    nrowProcessIndexTable <- length(atModel)
    if(length(afterIndex) == 0) {
        afterIndex <- nrowProcessIndexTable
    }
    
    # Add the process ID and as the process after 'afterIndex':
    before <- processIndexTable[seq_len(afterIndex), ]
    new <- data.table::data.table(
        processID = processID, 
        processName = processName, 
        modelName = modelName
    )
    if(afterIndex < nrowProcessIndexTable) {
        after <- processIndexTable[seq(afterIndex + 1, nrowProcessIndexTable), ]
    }
    else {
        after <- NULL
    }
    
    # Build the new processIndexTable:
    processIndexTable <- rbind(
        before, 
        new, 
        after
    )
    
    # Write the file:
    writeProcessIndexTable(projectPath = projectPath, processIndexTable = processIndexTable)
}


removeFromProcessIndexTable <- function(projectPath, modelName, processID) {
    
    # Get the process index file:
    processIndexTable <- readProcessIndexTable(projectPath = projectPath)
    
    # Remove the process:
    toRemove <- processIndexTable$modelName == modelName & processIndexTable$processID == processID
    toKeep <- !toRemove
    processIndexTable <- subset(processIndexTable, toKeep)
    
    # Write the file:
    writeProcessIndexTable(projectPath = projectPath, processIndexTable = processIndexTable)
}

rearrangeProcessIndexTable <- function(projectPath, modelName, processID, afterProcessID) {
    
    # Get the process index file:
    #processIndexTable <- readProcessIndexTable(projectPath = projectPath, modelName = modelName)
    processIndexTable <- readProcessIndexTable(projectPath = projectPath)
    processIndex <- seq_len(nrow(processIndexTable))
    
    # Add the process ID as the process after 'afterProcessID':
    toRearrange <- match(paste(modelName, processID), paste(processIndexTable$modelName, processIndexTable$processID))
    notToRearrange <- setdiff(processIndex, toRearrange)
    rearranged <- processIndexTable[toRearrange, ]
    rest <- processIndexTable[notToRearrange, ]
    
    if(!length(afterProcessID) || is.na(afterProcessID) || !nchar(afterProcessID)) {
        #afterProcessIndexInRest <- 0
        # Find the first process of the given model:
        firstProcessIndexOfTheModel <- min(processIndex[processIndexTable$modelName == modelName])
        afterProcessIndexInRest <- firstProcessIndexOfTheModel - 1
    }
    else {
        afterProcessIndexInRest <- which(rest$modelName %in% modelName & rest$processID %in% afterProcessID)
    }
    
    #afterProcessIndexInRest <- max(0, which(rest$modelName %in% modelName & rest$processID %in% afterProcessID))
    if(!length(afterProcessIndexInRest)) {
        return(NULL)
    }
    
    before <- rest[seq_len(afterProcessIndexInRest), ]
    if(afterProcessIndexInRest < nrow(rest)) {
        after <- rest[seq(afterProcessIndexInRest + 1, nrow(rest)), ]
    }
    else {
        after <- NULL
    }
    
    # Build the new processIndexTable:
    newProcessIndexTable <- rbind(
        before, 
        rearranged, 
        after
    )
    
    # Was there any change?:
    changed <- which(processIndexTable$processID != newProcessIndexTable$processID)
    
    # Write the file:
    if(any(changed)) {
        writeProcessIndexTable(projectPath = projectPath, processIndexTable = newProcessIndexTable)
        
        # Set the active process index as the first changed process minus 1 within the model:
        activeProcessIndex <- min(changed) - 1
        # This is hard tu grasp, but we re-define the activeProcessIndex here to be 0 if not in the given model (if the process har been moved to be the first process of the model, but still possibly with processes from other models before it):
        if(! activeProcessIndex %in% processIndex[processIndexTable$modelName == modelName]) {
            activeProcessIndex <- 0
        }
        
        # If reset to the start, return NA:
        if(activeProcessIndex == 0) {
            activeProcessID <- NA
        }
        else {
            activeProcessID <- processIndexTable$processID[activeProcessIndex]
        }
        
        return(activeProcessID)
    }
    else {
        return(NULL)
    }
    
}


modifyProcessNameInProcessIndexTable <- function(projectPath, modelName, processName, newProcessName) {
    # Get the process index file:
    processIndexTable <- readProcessIndexTable(projectPath = projectPath)
    
    # Modify the name of the process:
    toRename <- which(processIndexTable$modelName %in% modelName & processIndexTable$processName %in% processName)
    processIndexTable[toRename, processName := ..newProcessName]
    
    # Write the file:
    writeProcessIndexTable(projectPath = projectPath, processIndexTable = processIndexTable)
}


# Function to modify the function input of one specific model:
modifyProcessNameInFunctionInputs <- function(projectPath, modelName, processName, newProcessName) {
    
    # Update for all models from the specified model and onwards:
    modelNames <- getRstoxFrameworkDefinitions("stoxModelHierarchy")
    atModel <- which(modelNames == modelName)
    modelNames <- modelNames[seq(atModel, length(modelNames))]
    
    
    # Get the process index file:
    processTable <- getProcessesSansProcessData(
        projectPath = projectPath, 
        modelName = modelNames
    )
    nProcesses <- nrow(processTable)
    atProcess <- which(processTable$processName == newProcessName)
    
    # Modify the process name in the function inputs:
    if(atProcess < nProcesses) {
        for(index in seq(atProcess + 1, nProcesses)) {
            atProcessName <- sapply(processTable$functionInputs[[index]], function(x) any(processName == x))
            if(any(atProcessName)) {
                # Define the list of function inputs to modify:
                dataType <- names(processTable$functionInputs[[index]][atProcessName])
                insertList <- list(
                    newProcessName
                )
                names(insertList) <- dataType
                
                # Modify the function inputs to the new process name:
                modifyFunctionInputs(
                    projectPath = projectPath, 
                    modelName = processTable$modelName[[index]], 
                    processID = processTable$processID[[index]], 
                    newFunctionInputs = insertList
                )
                
            }
        }
    }
}


#getProcessIDFromStartEnd <- function(projectPath, modelName, startProcess = 1, endProcess = Inf) {
#    # Get the processIDs:
#    processIndexTable <- readProcessIndexTable(projectPath, modelName)
#    # Rstrict the startProcess and endProcess to the range of process indices:
#    startProcess <- max(1, startProcess)
#    endProcess <- min(nrow(processIndexTable), endProcess)
#    # Extract the requested process IDs:
#    processIDs <- processIndexTable[seq(startProcess, endProcess)]$processID
#    return(processIDs)
#}



##### Process table: #####
#' Functions to get the process table of a model.
#'
#' @inheritParams general_arguments
#' @param return.processIndex Logical: If TRUE include the process indices as a sequence starting from 1 in each model.
#' @param check.only.enabled Logical: If TRUE check input errors only for enabled procecsses.
#' 
#' @export
#' 
getProcessTable <- function(projectPath, modelName = NULL, startProcess = 1, endProcess = Inf, afterProcessID = NULL, beforeProcessID = NULL, argumentFilePaths = NULL, only.valid = TRUE, return.processIndex = FALSE) {
    
    # Maybe we should set only.valid to FALSE by default, just as is done in scanForModelError()???
    
    # Read the memory file paths once, and insert to the get* functions below to speed things up:
    if(length(argumentFilePaths) == 0) {
        #argumentFilePaths <- getArgumentFilePaths(projectPath, modelName = modelName)
        # Revert to getting info of all processes, since function outputs can be requested accross models:
        argumentFilePaths <- getArgumentFilePaths(projectPath)
    }
    
    # Get a table of all the processes including function inputs, parameters and input errors:
    processTable <- scanForModelError(
        projectPath = projectPath, 
        modelName = modelName, 
        startProcess = startProcess, 
        endProcess = endProcess, 
        afterProcessID = afterProcessID, 
        beforeProcessID = beforeProcessID, 
        argumentFilePaths = argumentFilePaths, 
        only.valid = only.valid, 
        return.processIndex = return.processIndex
    )
    # Return an empty data.table if the processTable is empty:
    if(nrow(processTable) == 0) {
        return(data.table::data.table())
    }
    
    # Check whether the data type can be shown in the map:
    processTable[, canShowInMap := getCanShowInMap(dataType = functionOutputDataType)]
    
    # Check whether the process returns process data:
    processTable[, hasProcessData := lapply(functionName, isProcessDataFunction)]
    
    # Add hasBeenRun:
    activeProcess <- getActiveProcess(projectPath = projectPath, modelName = modelName)
    processTable[, hasBeenRun := FALSE]
    if(!is.na(activeProcess$processID)) {
        activeProcessIndex <- getProcessIndexFromProcessID(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = activeProcess$processID
        )
        processTable[seq_len(min(activeProcessIndex, nrow(processTable))), hasBeenRun := TRUE]
    }
    
    return(processTable[])
}
#' 
#' @export
#' @rdname getProcessTable
#' 
scanForModelError <- function(projectPath, modelName = NULL, startProcess = 1, endProcess = Inf, afterProcessID = NULL, beforeProcessID = NULL, argumentFilePaths = NULL, only.valid = TRUE, return.processIndex = FALSE, check.only.enabled = TRUE) {
    
    # Read the memory file paths once, and insert to the get* functions below to speed things up:
    if(length(argumentFilePaths) == 0) {
        argumentFilePaths <- getArgumentFilePaths(projectPath)
    }
    
    # Get the processes:
    processTable <- getProcessesSansProcessData(
        projectPath = projectPath, 
        #modelName = modelName, 
        modelName = NULL, 
        startProcess = startProcess, 
        endProcess = endProcess, 
        afterProcessID = afterProcessID, 
        beforeProcessID = beforeProcessID, 
        argumentFilePaths = argumentFilePaths, 
        only.valid = only.valid, 
        return.processIndex = return.processIndex
    )
    # Return an empty data.table if the processTable is empty:
    if(nrow(processTable) == 0) {
        return(data.table::data.table())
    }
    
    # Add output data type:
    processTable$functionOutputDataType <- mapply(
        getDataType, 
        projectPath = projectPath, 
        #modelName = modelName, 
        modelName = processTable$modelName, 
        processID = processTable$processID, 
        MoreArgs = list(argumentFilePaths = argumentFilePaths), 
        SIMPLIFY = TRUE
    )
    
    
    # Run through the processes and detect model errors:
    processTable[, functionInputError := FALSE]
    for(model in modelName) {
        # Check only the given model:
        processIndex <- which(processTable$modelName == model)
        
        for(ind in processIndex) {
            if(length(processTable$functionInputs[[ind]])) {
                if(!check.only.enabled || isTRUE(processTable$enabled[[ind]])) {
                    processTable[ind, functionInputError := any(checkFunctionInputs(processTable[seq_len(ind), ]))]
                }
            }
        }
    }
    
    
    
    # Extract the requested model:
    if(length(modelName)) {
        toKeep <- processTable$modelName == modelName
        processTable <- subset(processTable, toKeep)
    }
    
    return(processTable)
}
#' 
#' @export
#' @rdname getProcessTable
#' 
getProcessesSansProcessData <- function(projectPath, modelName = NULL, startProcess = 1, endProcess = Inf, afterProcessID = NULL, beforeProcessID = NULL, argumentFilePaths = NULL, only.valid = FALSE, return.processIndex = FALSE, warn = TRUE) {
    
    # Read the memory file paths once, and insert to the get* functions below to speed things up:
    if(length(argumentFilePaths) == 0) {
        argumentFilePaths <- getArgumentFilePaths(projectPath)
    }
    
    # Get the processes:
    processTable <- getProcessAndFunctionNames(
        projectPath = projectPath, 
        modelName = modelName, 
        startProcess = startProcess, 
        endProcess = endProcess, 
        afterProcessID = afterProcessID, 
        beforeProcessID = beforeProcessID, 
        argumentFilePaths = argumentFilePaths, 
        return.processIndex = return.processIndex, 
        warn = warn
    )
    if(length(processTable) == 0) {
        return(processTable)
    }
    
    ##### (1) Add process parameters: #####
    processParameters <- mapply(
        getProcessParameters, 
        projectPath = projectPath, 
        #modelName = modelName, 
        modelName = processTable$modelName, 
        processID = processTable$processID, 
        MoreArgs = list(argumentFilePaths = argumentFilePaths), 
        SIMPLIFY = FALSE
    )
    processParameters <- data.table::rbindlist(processParameters)
    processTable <- data.table::data.table(
        processTable, 
        processParameters
    )
    
    ##### (2) Add function inputs: #####
    #functionInputs <- lapply(processTable$processID, function(processID) getFunctionInputs(projectPath, modelName, processID, only.valid = only.valid, argumentFilePaths = argumentFilePaths))
    functionInputs <- mapply(
        getFunctionInputs, 
        projectPath = projectPath, 
        modelName = processTable$modelName, 
        processID = processTable$processID, 
        MoreArgs = list(
            only.valid = only.valid, 
            argumentFilePaths = argumentFilePaths
        ), 
        SIMPLIFY = FALSE
    )
    processTable[, functionInputs := ..functionInputs]
    
    ##### (3) Add function parameters: #####
    #functionParameters <- lapply(processTable$processID, function(processID) getFunctionParameters(projectPath, modelName, processID, only.valid = only.valid, argumentFilePaths = argumentFilePaths))
    functionParameters <- mapply(
        getFunctionParameters, 
        projectPath = projectPath, 
        modelName = processTable$modelName, 
        processID = processTable$processID, 
        MoreArgs = list(
            only.valid = only.valid, 
            argumentFilePaths = argumentFilePaths
        ), 
        SIMPLIFY = FALSE
    )
    processTable[, functionParameters := ..functionParameters]
    
    return(processTable)
}
#' 
#' @export
#' @rdname getProcessTable
#' 
getProcessAndFunctionNames <- function(projectPath, modelName = NULL, startProcess = 1, endProcess = Inf, afterProcessID = NULL, beforeProcessID = NULL, argumentFilePaths = NULL, return.processIndex = FALSE, warn = TRUE) {
    
    # Read the memory file paths once, and insert to the get* functions below to speed things up:
    if(length(argumentFilePaths) == 0) {
        argumentFilePaths <- getArgumentFilePaths(projectPath)
    }
    
    ##### (1) Get the table of process name and ID: #####
    processIndexTable <- readProcessIndexTable(
        projectPath = projectPath, 
        modelName = modelName, 
        startProcess = startProcess, 
        endProcess = endProcess, 
        return.processIndex = return.processIndex, 
        warn = warn
    )
    # Return an empty data.table if the processIndexTable is empty:
    if(nrow(processIndexTable) == 0) {
        return(data.table::data.table())
    }
    
    # If afterProcessID and beforeProcessID are given, subset the table to up until the reuqested beforeProcessID, if given:
    processIDs <- getProcessIDsFromBeforeAfter(
        projectPath = projectPath, 
        afterProcessID = afterProcessID, 
        beforeProcessID = beforeProcessID, 
        processIndexTable = processIndexTable
    )
    processIndices <- match(processIDs, processIndexTable$processID)
    processIndexTable <- processIndexTable[processIndices, ]
    # Return an empty data.table if the processIndexTable is empty, after finidng the prior processes:
    if(nrow(processIndexTable) == 0) {
        return(data.table::data.table())
    }
    
    # Add the projectPath:
    processIndexTable[, projectPath := projectPath]
    
    ##### (2) Add function names: #####
    functionName <- mapply(
        getFunctionName, 
        projectPath = projectPath, 
        #modelName = modelName, 
        modelName = processIndexTable$modelName, 
        processID = processIndexTable$processID, 
        MoreArgs = list(argumentFilePaths = argumentFilePaths)
    )
    processIndexTable[, functionName := ..functionName]
    
    return(processIndexTable)
}

getProcessIDsFromBeforeAfter <- function(projectPath, afterProcessID = NULL, beforeProcessID = NULL, processIndexTable) {
    # First get the model hierarchy. This enables us to search for processes in one model before the other: 
    stoxModelHierarchy <- getRstoxFrameworkDefinitions("stoxModelHierarchy")
    
    # Get a vector of the process IDs ordered by model:
    orderedProcessIDs <- unlist(lapply(stoxModelHierarchy, function(thisModelName) processIndexTable[modelName == thisModelName, processID]))
    
    if(length(beforeProcessID)) {
        atProcessID <- which(beforeProcessID == orderedProcessIDs)
        if(length(atProcessID) == 0) {
            stop("The processID specified in 'beforeProcessID' does not exist in the project ", projectPath, ".")
        }
        orderedProcessIDs <- orderedProcessIDs[seq_len(atProcessID - 1)]
    }
    # Remove the table up until the reuqested afterProcessID, if given:
    if(length(afterProcessID)) {
        atProcessID <- which(afterProcessID == orderedProcessIDs)
        if(length(atProcessID) == 0) {
            stop("The processID specified in 'afterProcessID' does not exist in the project ", projectPath, ".")
        }
        orderedProcessIDs <- orderedProcessIDs[- seq_len(atProcessID)]
    }
    
    return(orderedProcessIDs)
}



checkFunctionInput <- function(functionInput, functionInputDataType, processIndexTable, processName) {
    # Expect an error, and return FALSE if all checks passes:
    functionInputError <- TRUE
    if(length(functionInput)) {
        # (0) Chech that the function input is a string with positive number of characters:
        if(length(functionInput) && !is.character(functionInput)) {
            warning("StoX: Function input to the process ", processName, " must be a character string (", functionInputDataType, ").")
        }
        # (1) Error if empty string:
        else if(nchar(functionInput) == 0) {
            warning("StoX: Function input to the process ", processName, " must be a non-empty character string (data type ", functionInputDataType, ").")
        }
        # (2) Error if not the name of a previous process:
        else if(! functionInput %in% processIndexTable$processName) {
            warning("StoX: Function input ", functionInput, " to the process ", processName, " is not the name of a previous process (data type ", functionInputDataType, ").")
        }
        else {
            atRequestedPriorProcess <- which(functionInput == processIndexTable$processName)
            
            
            outputDataTypeOfRequestedPriorProcess <- getStoxFunctionMetaData(processIndexTable$functionName[atRequestedPriorProcess], "functionOutputDataType")
            
            # (3) Error if the previous process returns the wrong data type:
            if(! functionInputDataType %in% outputDataTypeOfRequestedPriorProcess) {
                warning("StoX: Function input to the process ", processName, " from process ", processIndexTable$processName[atRequestedPriorProcess], " does not return the correct data type (", functionInputDataType, ").")
            }
            # (4) Error if the previous process is not enabled:
            else if(!processIndexTable$enabled[atRequestedPriorProcess]) {
                warning("StoX: The process ", processIndexTable$processName[atRequestedPriorProcess], " is used by the process ", processIndexTable$processName[[nrow(processIndexTable)]], " but is not enabled.")
            }
            # (5) Error if the previous process has input error:
            else if(processIndexTable$functionInputError[atRequestedPriorProcess]) {
                warning("StoX: The process ", processIndexTable$processName[atRequestedPriorProcess], " has input error.")
            }
            else {
                functionInputError <- FALSE
            }
        }
    }
    else {
        functionInputError <- FALSE
    }
    
    return(functionInputError)
}

checkFunctionInputs <- function(processIndexTable) {
    # Get the function input name and value pairs:
    atEnd <- nrow(processIndexTable)
    functionInput <- processIndexTable$functionInputs[[atEnd]]
    functionInputDataType <- names(processIndexTable$functionInputs[[atEnd]])
    processName <- processIndexTable$processName[[atEnd]]
    functionInputError <- mapply(
        checkFunctionInput, 
        functionInput = functionInput, 
        functionInputDataType = functionInputDataType, 
        MoreArgs = list(
            processName = processName, 
            processIndexTable = processIndexTable
        )
    )
    
    return(functionInputError)
}


isFunctionInput <- function(parameter) {
    # Get the valid data types (model data and process data), and check whether the inputs are in these:
    stoxDataTypes <- getRstoxFrameworkDefinitions("stoxDataTypes")
    parameter %in% stoxDataTypes$functionOutputDataType
}


createEmptyProcess <- function(modelName = "baseline", processName = NULL) {
    # Get the default process with empty fields for project and function name, process data, and function parameters and inputs:
    process <- getRstoxFrameworkDefinitions("processDefault")[[modelName]]
    # Possibly add the given process name (this is done here since creating a default process name is not always needed or wanted):
    if(length(processName) && !is.na(processName) && nchar(processName) > 0) {
        process$processName <- processName
    }
    process
}


# Unfinished, needs all process properties:
# createProcess <- function(modelName = "baseline", values) {
#     # Get the default process with empty fields for project and function name, process data, and function parameters and inputs:
#     process <- getRstoxFrameworkDefinitions("processDefault")[[modelName]]
#     # Add process name:
#     if(length(values$processName) && !is.na(values$processName) && nchar(values$processName) > 0) {
#         process$processName <- values$processName
#     }
#     # Add process parameters:
#     if(length(values$processName) && !is.na(values$processName) && nchar(values$processName) > 0) {
#         process$processName <- values$processName
#     }
#     # Add process name:
#     if(length(values$processName) && !is.na(values$processName) && nchar(values$processName) > 0) {
#         process$processName <- values$processName
#     }
#     # Add process name:
#     if(length(values$processName) && !is.na(values$processName) && nchar(values$processName) > 0) {
#         process$processName <- values$processName
#     }
#     # Add process name:
#     if(length(values$processName) && !is.na(values$processName) && nchar(values$processName) > 0) {
#         process$processName <- values$processName
#     }
#     # Add process name:
#     if(length(values$processName) && !is.na(values$processName) && nchar(values$processName) > 0) {
#         process$processName <- values$processName
#     }
#     
#     
#     
#     process
# }


# Function to detect which of the process parameters to include/exclude:
getPossibleProcessParameterNames <- function() {
    # Before this funciton was functionName specific, but all process parameters are included for all processes, and then irrelevant ones are hidden in process properies in StoX.
    # getPossibleProcessParameterNames <- function(functionName) {
        
    # Get the possible process parameters:
    processParameters <- getRstoxFrameworkDefinitions("processParameters")
    possibleProcessParameters <- names(processParameters)
    
    ## Remove "showInMap" if relevant:
    #if(!getCanShowInMap(functionName)) {
    #    possibleProcessParameters <- setdiff(possibleProcessParameters, "showInMap")
    #}
    
    # Return the vector of process parameters names:
    possibleProcessParameters
}


applyEmptyFunction <- function(process) {
    process$functionName <- ""
    process$functionInputs <- list()
    process$functionParameters <- list()
    return(process)
}


# This funciton is quite central, as it is responsible of setting the default values of functions. Only the function inputs and parameters introduced to a process using setFunctionName() can be modified:
setFunctionName <- function(process, newFunctionName, add.defaults = FALSE) {
    
    # If empty function name, return empty list:
    if(length(newFunctionName) == 0 || nchar(newFunctionName) == 0) {
        process <- applyEmptyFunction(process)
    }
    else {
        # Validate functionName:
        newFunctionName <- validateFunction(newFunctionName)
        
        # Insert the function name:
        process$functionName <- newFunctionName
        # Get the parameters to display, and their defaults:
        defaults <- getStoxFunctionParameterDefaults(process$functionName)
        
        # Detect which parameters are data types, which identifies them as function inputs (outputs from other processes):
        areInputs <- isFunctionInput(names(defaults))
        
        if(!add.defaults) {
            #areNonEmptyString <- sapply(defaults, function(x) length(x) && is.character(x))
            #if(any(areNonEmptyString)) {
            #    defaults[areNonEmptyString] <- lapply(defaults[areNonEmptyString], function(x) character(0))
            #}
            #
        }
        else {
            # Add also defaults from the stoxFunctionAttributes:
            functionParameterDefaults <- getStoxFunctionMetaData(newFunctionName, "functionParameterDefaults", showWarnings = FALSE)
            if(length(functionParameterDefaults)) {
                for(name in names(functionParameterDefaults)) {
                    defaults[[name]] <- functionParameterDefaults[[name]]
                }
            }
        }
        
        # Split the defaults into function parameters and function inputs:
        process$functionParameters <- defaults[!areInputs]
        process$functionInputs <- defaults[areInputs]
    }
    
    # Delete the processData, since these are no longer valid for the new function:
    process$processData <- list()
    
    # Return the process:
    process
}

onlyValidCharactersInProcessnName <- function(newProcessName) {
    # Check that the new process name ha one or more characters:
    positiveLength <- nchar(newProcessName) > 0
    
    # Check also for invalid characters:
    indValidCharacters <- gregexpr(getRstoxFrameworkDefinitions("validProcessNameSet"), newProcessName)[[1]]
    indInvalidCharacters <- setdiff(seq_len(nchar(newProcessName)), indValidCharacters)
    
    if(!positiveLength) {
        stop("Process names cannot be an empty string")
        #FALSE
    }
    else if(length(indInvalidCharacters)) {
        warning("Process names can only contain lower and upper letters, numbers, dot and underscore. Contained ", paste(strsplit(newProcessName, "")[indInvalidCharacters], collapse = ", "))
        FALSE
    }
    else {
        TRUE
    }
}

processNameExists <- function(processName, projectPath) {
    # Check the process names of the model:
    processIndexTable <- readProcessIndexTable(projectPath = projectPath)
    exists <- processName %in% processIndexTable$processName
    
    return(exists)
}



checkProcessName <- function(processName, projectPath, strict = TRUE) {
    
    # If the process name is not valid:
    # Check the processName only if strict is FALSE or TRUE. If NULL ignore the test, which is used when opening a project, since this test reads the ****************************:
    if(length(strict) && (!onlyValidCharactersInProcessnName(processName) || processNameExists(processName, projectPath))) {
        if(strict) {
            stop("Invalid or already used process name \"", processName, "\" of project ", projectPath)
        }
        else {
            newProcessName <- getNewDefaultProcessName(projectPath)
            warning("Invalid or already used process name \"", processName, "\" of project ", projectPath, ". Channged from ", processName, " to ", newProcessName, ".")
            processName <- newProcessName
        }
    }
    
    return(processName)
}



setListElements <- function(list, insertList, projectPath, modelName, processID) {
    
    # Report a warning for elements not present in the list:
    insertNames <- names(insertList)
    #presentNames <- names(list)
    #valid <- insertNames %in% presentNames
    
    # This warning made more sense when the contents of setListElements() was included in every function using it, since the process and function name was available. 
    
    #if(any(!valid)) {
    #    # Warn the user that there are invalid list elements:
    #    warning(
    #        "Removed the following unrecognized parameters for the function ", 
    #        getFunctionName(projectPath, modelName, processID), 
    #        " of process ", 
    #        getProcessName(projectPath, modelName, processID), 
    #        ": ", 
    #        paste(insertNames[!valid], collapse = ", "),
    #        if(length(presentNames)) paste0(" (Valid parameters: ", paste(presentNames, sep = ", "), ")")
    #    )
    #    # Keep only the new list elements that are present in the list:
    #    insertNames <- insertNames[valid]
    #}
    
    # Insert the list elements (one by one for safety):
    if(length(insertNames)) {
        for(ind in seq_along(insertList)) {
            # Added this if statement on 2020-04-03 (and re-added after some rebase trouble on 2020-05-25), since it prevents parameters from being deleted:
            if(!is.null(insertList[[ind]])) {
                list[[names(insertList[ind])]] <- insertList[[ind]]
            }
        }
    }
    
    list
}


##### Functions for modifying individual process arguments. These are called in the exported function modifyProcess(): #####
modifyFunctionName <- function(projectPath, modelName, processID, newFunctionName, archive = TRUE, add.defaults = FALSE) {
    
    # Get the project description:
    process <- getProcessArguments(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Change the function name only if different from the existing:
    if(!identical(process$functionName, newFunctionName)) {
        # Error if the function name is not character:
        if(!is.character(newFunctionName)) {
            stop("The function name must be a character string of the type packageName::functionName")
        }
        # Set the function name, and the corresponding default function inputs and parameters, as well as removing any process parameters that should not be included (showImMap):
        process <- setFunctionName(process, newFunctionName, add.defaults = add.defaults)
        
        # Store the changes:
        setProcessMemory(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            process = process, 
            archive = archive
        )
        
        # Return a flag TRUE if the function name was changed: 
        return(TRUE)
    }
    else {
        return(FALSE)
    }
    #process
}
#emptyFunctionName <- function(projectPath, modelName, processID, archive = TRUE) {
#    modifyFunctionName(
#        projectPath = projectPath, 
#        modelName = modelName, 
#        processID = processID, 
#        newFunctionName = list(), 
#        archive = archive, 
#        add.defaults = FALSE
#    )
#}
modifyProcessName <- function(projectPath, modelName, processID, newProcessName, archive = TRUE, strict = TRUE, update.functionInputs = TRUE) {
    
    # Get the current process name:
    processName <- getProcessName(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Change the process name only if different from the existing:
    if(!identical(processName, newProcessName)) {
        # Validate the new process name (for invalid characters):
        #validProcessName <- validateProcessName(
        #    projectPath = projectPath, 
        #    modelName = modelName, 
        #    newProcessName = newProcessName, 
        #    strict = TRUE
        #)
        
        newProcessName <- checkProcessName(
            newProcessName, 
            projectPath = projectPath, 
            strict = strict
        )
        
        
        #if(validProcessName) {
            setProcessMemory(
                projectPath = projectPath, 
                modelName = modelName, 
                processID = processID, 
                argumentName = "processName", 
                argumentValue = newProcessName, 
                archive = archive
            )
        #}
        
        # Modify the process name also in the proces index table:
        modifyProcessNameInProcessIndexTable(projectPath, modelName, processName, newProcessName)
        
        # Change the process name in all relevant function inputs of consecutive processes:
        if(update.functionInputs) {
            modifyProcessNameInFunctionInputs(projectPath, modelName, processName, newProcessName)
        }
        
        # Return a flag TRUE if the process name was changed: 
        return(TRUE)
    }
    else {
        return(FALSE)
    }
    
    #processName
}
emptyProcessName <- function(projectPath, modelName, processID, archive = TRUE, strict = TRUE) {
    modifyProcessName(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        newProcessName = list(), 
        archive = archive, 
        strict = strict
    )
}
modifyFunctionParameters <- function(projectPath, modelName, processID, newFunctionParameters, archive = TRUE) {
    
    # Get the function parameters:
    functionParameters <- getFunctionParameters(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )

    # Modify any file or directory paths to relative paths if possible:
    newFunctionParameters <- convertToRelativePaths(
        functionParameters = newFunctionParameters, 
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Modify the funciton parameters:
    modifiedFunctionParameters <- setListElements(
        list = functionParameters, 
        insertList = newFunctionParameters, 
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Store the changes:
    if(!identical(functionParameters, modifiedFunctionParameters)) {
        setProcessMemory(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            argumentName = "functionParameters", 
            argumentValue = list(modifiedFunctionParameters),  # We need to list this to make it correspond to the single value of the argumentName parameter.
            archive = archive
        )
        
        # Return a flag TRUE if the function parameters were changed: 
        return(TRUE)
    }
    else {
        return(FALSE)
    }
    
    #modifiedFunctionParameters
}
emptyFunctionParameters <- function(projectPath, modelName, processID, functionParameterNames, archive = TRUE) {
    modifyFunctionParameters(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        newFunctionParameters = structure(rep(list(list()), length(functionParameterNames)), names = functionParameterNames), 
        archive = archive
    )
}
modifyFunctionInputs <- function(projectPath, modelName, processID, newFunctionInputs, archive = TRUE) {
    
    # Get the function inputs:
    functionInputs <- getFunctionInputs(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Modify the funciton inputs:
    modifiedFunctionInputs <- setListElements(
        list = functionInputs, 
        insertList = newFunctionInputs, 
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Store the changes:
    if(!identical(functionInputs, modifiedFunctionInputs)) {
        setProcessMemory(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            argumentName = "functionInputs", 
            argumentValue = list(modifiedFunctionInputs),  # We need to list this to make it correspond to the single value of the argumentName parameter.
            archive = archive
        )
    
        # Return a flag TRUE if the function inputs were changed: 
        return(TRUE)
    }
    else {
        return(FALSE)
    }
    
    #modifiedFunctionInputs
}
emptyFunctionInputs <- function(projectPath, modelName, processID, functionInputsNames, archive = TRUE) {
    modifyFunctionInputs(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        newFunctionInputs = structure(rep(list(list()), length(functionInputsNames)), names = functionInputsNames), 
        archive = archive
    )
}
modifyProcessParameters <- function(projectPath, modelName, processID, newProcessParameters, archive = TRUE) {
    
    # Get the function inputs:
    processParameters <- getProcessParameters(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Modify the funciton parameters:
    modifiedProcessParameters <- setListElements(
        list = processParameters, 
        insertList = newProcessParameters, 
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Store the changes:
    if(!identical(processParameters, modifiedProcessParameters)) {
        setProcessMemory(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            argumentName = "processParameters", 
            argumentValue = list(modifiedProcessParameters),  # We need to list this to make it correspond to the single value of the argumentName parameter.
            archive = archive
        )
    
        # Return a flag TRUE if the process parameters were changed: 
        return(TRUE)
    }
    else {
        return(FALSE)
    }
    
    #modifiedProcessParameters
}
modifyProcessData <- function(projectPath, modelName, processID, newProcessData, archive = TRUE, purge.processData = FALSE) {
    
    # Get the process data:
    processData<- getProcessData(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        check.activeProcess = FALSE
    )
    
    # Modify the process data:
    if(purge.processData) {
        modifiedProcessData <- newProcessData
    }
    else {
        modifiedProcessData <- setListElements(
            list = processData, 
            insertList = newProcessData, 
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID
        )
    }
    
    # Store the changes:
    #if(!identical(processData, modifiedProcessData)) {
    # At 2021-01-14 this was eased to isTRUE(all.equal()) instead of identical, as identical may react to non-essential attributes:
    if(!isTRUE(all.equal(processData, modifiedProcessData))) {
        setProcessMemory(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            argumentName = "processData", 
            argumentValue = list(modifiedProcessData),  # We need to list this to make it correspond to the single value of the argumentName parameter.
            archive = archive
        )
    
        # Return a flag TRUE if the process data were changed: 
        return(TRUE)
    }
    else {
        return(FALSE)
    }
    
    #modifiedProcessData
}


# Function returning a logical vector with TRUE for function parameters which are file paths as per the format attribute:
detectFilePaths <- function(functionParameters, projectPath = projectPath, modelName = modelName, processID = processID) {
    # Get the function name and the function parameter formats:
    functionName <- getFunctionName(projectPath = projectPath, modelName = modelName, processID = processID)
    functionParameterFormat <- getStoxFunctionMetaData(functionName, "functionParameterFormat")
    
    # Detect file path formats:
    areFilePathsAndNonEmpty <- functionParameterFormat[names(functionParameters)] %in% c("filePath", "filePaths", "directoryPath") & lengths(functionParameters) > 0
    areFilePathsAndNonEmpty
}

# Function to detect function parameter format filePath, filePaths or directoryPath, and convert to relative paths if the projectPath is present in the paths:
convertToRelativePaths <- function(functionParameters, projectPath, modelName, processID, warn = FALSE) {
    
    # Detect the file paths:
    areFilePathsAndNonEmpty <- detectFilePaths(
        functionParameters = functionParameters, 
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Get relative paths:
    if(any(areFilePathsAndNonEmpty)) {
        functionParameters[areFilePathsAndNonEmpty] <- lapply(
            functionParameters[areFilePathsAndNonEmpty], 
            getRelativePaths, 
            projectPath = projectPath, 
            warn = warn
        )
    }
    
    functionParameters
}

# Function to attempt to convert to relative path:
getRelativePath <- function(filePath, projectPath, warn = FALSE) {
    # Expand the paths:
    projectPath <- path.expand(projectPath)
    filePath <- path.expand(filePath)
    
    # Remove any double slashes:
    projectPath <- gsub("//", "/", projectPath)
    filePath <- gsub("//", "/", filePath)
    
    # Also translate escaped backslash to single forwardslash:
    projectPath <- gsub("\\\\", "/", projectPath)
    filePath <- gsub("\\\\", "/", filePath)
    
    # Check whether the filePath is a relative path already:
    fullFilePath <- file.path(projectPath, filePath)
    if(file.exists(fullFilePath) && isFALSE(file.info(fullFilePath)$isdir)) {
        return(filePath)
    }
    
    # If the projectPath is in the filePath, convert to a relative file path:
    # 2022-11-22: Make sure that the projectPath ends with exactly 1 "/":
    projectPath <- paste0(gsub("/*$", "", projectPath), "/")
    if(startsWith(filePath, projectPath)) {
        filePath <- substring(filePath, nchar(projectPath) + 1)
    }
    else if(warn) {
        warning("StoX: The specified file ", filePath, " is not present in the project folder (", projectPath, ")")
    }
    filePath
}

getRelativePaths <- function(filePaths, projectPath, warn = FALSE) {
    unname(sapply(filePaths, getRelativePath, projectPath = projectPath, warn = warn))
}



# Function to detect function parameter format filePath, filePaths or directoryPath, and convert to absolute paths for use in functions:
getAbsolutePaths <- function(functionParameters, projectPath, modelName, processID) {
    # Detect the file paths:
    areFilePathsAndNonEmpty <- detectFilePaths(
        functionParameters = functionParameters, 
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Get absolute paths:
    if(any(areFilePathsAndNonEmpty)) {
        functionParameters[areFilePathsAndNonEmpty] <- lapply(
            functionParameters[areFilePathsAndNonEmpty], 
            getAbsolutePath, 
            projectPath = projectPath
        )
    }
    
    functionParameters
}

getAbsolutePath <- function(filePath, projectPath) {
    
    getAbsolutePathOne <- function(filePath, projectPath) {
        # Check first whether the file exists as a relative path:
        absolutePath <- file.path(projectPath, filePath)
        if(file.exists(absolutePath)) {
            absolutePath
        }
        else if(file.exists(filePath)) {
            filePath
        }
        else {
            #warning("StoX: The file ", filePath, " does not exist.")
            filePath
        }
    }
    
    # Check first whether the file exists as a relative path:
    sapply(filePath, getAbsolutePathOne, projectPath = projectPath)
}

#' Modify a process
#' 
#' @inheritParams general_arguments
#' @inheritParams addProcess
#' @param newValues A list named by the elements to modify (see getRstoxFrameworkDefinitions("processProperties") for possible elements), holding the values to modify to (e.g., list(functionParameter = list(DefinitionMethod = "Stratum"))).
#' @param purge.processData Logical: If TRUE replace process data entirely.
#' @param update.functionInputs Logical: If TRUE update the process name in the function inputs to other processes.
#' 
#' @export
#' 
modifyProcess <- function(projectPath, modelName, processName, newValues, archive = TRUE, add.defaults = FALSE, purge.processData = FALSE, strict = TRUE, update.functionInputs = TRUE) {
    
    # The values of the process must be changed in the following order:
    # 1. Function name
    # 2. Function parameters
    # 2. Function inputs
    # 1. Process name
    # 1. Process parameters
    # 1. Process data
    if(!isOpenProject(projectPath)) {
        warning("StoX: The project ", projectPath, " is not open. Use RstoxFramework::openProject() to open the project.")
        return(NULL)
    }
    
    # Get process ID from process name:
    processID <- getProcessIDFromProcessName(
        projectPath = projectPath, 
        modelName = modelName, 
        processName = processName
    )$processID
    
    # Output a flag of TRUE if a modification occurred:
    modified <- FALSE
    
    # Function name:
    if(length(newValues$functionName)) {
        modified <- modified | modifyFunctionName(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            newFunctionName = newValues$functionName, 
            archive = archive, 
            add.defaults = add.defaults
        )
    }
    
    # Function parameters:
    if(length(newValues$functionParameters)) {
        modified <- modified | modifyFunctionParameters(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            newFunctionParameters = newValues$functionParameters, 
            archive = archive
        )
    }
    
    # Function inputs:
    if(length(newValues$functionInputs)) {
        modified <- modified | modifyFunctionInputs(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            newFunctionInputs = newValues$functionInputs, 
            archive = archive
        )
    }
    
    # Process name:
    if(length(newValues$processName)) {
        modified <- modified | modifyProcessName(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            newProcessName = newValues$processName, 
            archive = archive, 
            strict = strict, 
            update.functionInputs = update.functionInputs
        )
    }
    
    # Process parameters:
    if(length(newValues$processParameters)) {
        modified <- modified | modifyProcessParameters(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            newProcessParameters = newValues$processParameters, 
            archive = archive
        )
    }
    
    # Process data:
    if(length(newValues$processData)) {
        modified <- modified | modifyProcessData(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID, 
            newProcessData = newValues$processData, 
            archive = archive, 
            purge.processData = purge.processData
        )
    }
    
    return(modified)
}


#' Modify a model
#' 
#' @inheritParams general_arguments
#' @inheritParams addProcess
#' @inheritParams modifyProcess
#' 
modifyModel <- function(projectPath, modelName, newValues, add.defaults = FALSE, purge.processData = FALSE, strict = TRUE, update.functionInputs = TRUE) {
    
    if(!isOpenProject(projectPath)) {
        warning("StoX: The project ", projectPath, " is not open. Use RstoxFramework::openProject() to open the project.")
        return(NULL)
    }
    
    # Find the processes to modify:
    processIndexTable <- readProcessIndexTable(projectPath = projectPath, modelName = modelName)
    if(!nrow(processIndexTable)) {
        return(FALSE)
    }
    thisModelName <- processIndexTable$modelName[1]
    newValues <- subset(newValues, names(newValues) %in% processIndexTable$processName)
    
    # Modify:
    mapply(modifyProcess, 
           projectPath = projectPath, 
           modelName = thisModelName, 
           processName = names(newValues), 
           newValues = newValues, 
           archive = FALSE, 
           add.defaults = add.defaults, 
           purge.processData = purge.processData, 
           strict = strict, 
           update.functionInputs = update.functionInputs
    )
}


#' Modify a project
#' 
#' @inheritParams general_arguments
#' @inheritParams addProcess
#' @inheritParams modifyProcess
#' 
#' @export
#' 
modifyProject <- function(projectPath, modelNames = getRstoxFrameworkDefinitions("stoxModelNames"), newValues, add.defaults = FALSE, purge.processData = FALSE, strict = TRUE, update.functionInputs = TRUE) {
    
    if(isOpenProject(projectPath)) {
        warning("You are trying to modify the already open project ", projectPath, ". Close the project with RstoxFramework::closeProject() first and then retry.")
        return(NULL)
    }
    else {
        openProject(projectPath)
        on.exit(closeProject(projectPath, save = TRUE))
    }
    
    # Modify:
    output <- lapply(modelNames, function(modelName) modifyModel(
        projectPath = projectPath, 
        modelName = , modelName, 
        newValues = newValues, 
        add.defaults = add.defaults, 
        purge.processData = purge.processData, 
        strict = strict, 
        update.functionInputs = update.functionInputs
    )
    )
    
    return(output)
}



#' Modify multiple projects
#' 
#' @inheritParams general_arguments
#' @inheritParams addProcess
#' @inheritParams modifyProcess
#' 
#' @export
#' 
modifyProjects <- function(projectPaths, modelNames = getRstoxFrameworkDefinitions("stoxModelNames"), newValues, add.defaults = FALSE, purge.processData = FALSE, strict = TRUE, update.functionInputs = TRUE) {
    
    # Modify:
    lapply(projectPaths, function(projectPath) modifyProject(
        projectPath = projectPath, 
        modelNames = , modelNames, 
        newValues = newValues, 
        add.defaults = add.defaults, 
        purge.processData = purge.processData, 
        strict = strict, 
        update.functionInputs = update.functionInputs
        )
    )
}



#' Remove a StoX process
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
removeProcesses <- function(projectPath, processNames) {
    
    if(isOpenProject(projectPath)) {
        warning("You are trying to modify the already open project ", projectPath, ". Close the project with RstoxFramework::closeProject() first and then retry.")
        return(NULL)
    }
    else {
        openProject(projectPath)
        on.exit(closeProject(projectPath, save = TRUE))
    }
    
    # Get model names and process IDs:
    processIndexTable <- readProcessIndexTable(projectPath = projectPath)
    if(!nrow(processIndexTable)) {
        return(FALSE)
    }
    
    notPresent <- setdiff(processNames, processIndexTable$processName)
    if(length(notPresent)) {
        warning("The following processes do not exist and can therefore not be removed from the project ", projectPath, ": ", paste(notPresent, collapse = ", "))
    }
    
    
    # Subset by the specified process names:
    processIndexTable <- subset(processIndexTable, processName %in% processNames)
    
    
    output <- mapply(removeProcess, projectPath = projectPath, modelName = processIndexTable$modelName, processID = processIndexTable$processID)
    
    
    return(output)
}




















# Function to format a process as read from the project.json
# Convert JSON input to list:
formatProcess <- function(process) {
    
    # The input must be a list containing all of the elements functionName, processName, processParameters, functionInputs, functionParameters and processData:
    if(!isProcess(process)) {
        warning("The input 'process' is not a StoX process. Returning unchanged.")
        return(process)
    }
    
    # Make sure functionName is character:
    process$functionName <- formatFunctionName(process$functionName)
    
    # Make sure processName is character:
    process$processName <- formatProcessName(process$processName)
    
    # Make sure all processParameters are logical:
    process$processParameters <- formatProcessParameters(process$processParameters)
    
    # Make sure all functionInputs are character:
    process$functionInputs <- formatFunctionInputs(process$functionInputs)
    
    # Set the type defined by the StoX function to the function parameters:
    process$functionParameters <- formatFunctionParameters(process$functionParameters, functionName = process$functionName)
    
    # Format the process data::
    process$processData <- formatProcessData(process$processData)
    
    return(process)
}



formatFunctionName <-  function(functionName) {
    #if(length(functionName) && !is.character(functionName)) {
    if(!is.character(functionName)) {
        functionName <- as.character(functionName)
    }
    return(functionName)
}

formatProcessName <-  function(processName) {
    #if(length(processName) && !is.character(processName)) {
    if(!is.character(processName)) {
        processName <- as.character(processName)
    }
    return(processName)
}

formatProcessParameters <-  function(processParameters) {
    # All process parameters are logical:
    notLogical <- !sapply(processParameters, is.logical)
    if(any(notLogical)) {
        processParameters[notLogical] <- lapply(processParameters[notLogical], as.logical)
    }

    return(processParameters)
}

formatFunctionInputs <-  function(functionInputs) {
    notCharacterOrEmpty <- sapply(functionInputs, function(x) length(x) && !is.character(x))
    if(any(notCharacterOrEmpty)) {
        functionInputs[notCharacterOrEmpty] <- lapply(functionInputs[notCharacterOrEmpty], as.character)
    }
    return(functionInputs)
}

formatFunctionParameters <-  function(functionParameters, functionName, projectPath = NULL, modelName = NULL, processID = NULL) {
    
    # Simplify vectors, matrices and data.frames using the jsonlite package:
    functionParameters <- simplifyListReadFromJSON(functionParameters)
    
    if(length(functionParameters)) {
        
        StoxFunctionParameterFormals <- getStoxFunctionParameterFormals(functionName)
        
        if(is.list(functionParameters)) {
            # Get present and invalid function parameters:
            present <- intersect(
                names(functionParameters), 
                names(StoxFunctionParameterFormals)
            )
            invalid <- setdiff(
                names(functionParameters), 
                names(StoxFunctionParameterFormals)
            )
            
            # Warning if there are parameters not specified in the function definition:
            if(length(invalid)) {
                warning("StoX: The following functionParameters are not specified in the definition of function ", functionName, ": ", paste(invalid, collapse = ", "))
            }
            # Change class to the defined class:
            else if(length(present)) {
                for(this in present) {
                    # If the defined class is not NULL, or if it is NULL and the property has length 0, apply the defined class:
                    
                    classIsDefined <- !is.null(StoxFunctionParameterFormals[[this]])
                    NULLDefinedAndEmptyProperty <- 
                        is.null(StoxFunctionParameterFormals[[this]]) && 
                        length(functionParameters[[this]]) == 0
                    table <- identical(getRelevantClass(StoxFunctionParameterFormals[[this]]), "data.table")
                    emptyTable <- table && length(functionParameters[[this]]) == 0
                    differingClass <- getRelevantClass(functionParameters[[this]]) != getRelevantClass(StoxFunctionParameterFormals[[this]])
                    
                    # Special case for NULL:
                    if(NULLDefinedAndEmptyProperty) {
                        warning("StoX: Function parameter ", this, " of function ", functionName, " is not declared with a value in the function definition. Please inform the developers that they should declare the parameter, e.g. with double(), character() etc to define the type of the parameter. NULL should be avoided.")
                        #functionParameters[this] <- list(NULL)
                    }
                    # ... and for empty data.table:
                    else if(emptyTable) {
                        functionParameters[[this]] <- data.table::data.table()
                    }
                    else if(table) {
                        functionParameters[[this]] <- data.table::as.data.table(functionParameters[[this]])
                        
                        # If read from the GUI, format using the functionParameterFormats:
                        if(length(projectPath) && length(modelName) && length(processID)) {
                            thisFormat <- getFunctionParameterFormats(functionName)[[this]]
                            
                            variableTypes <- getParameterFormatElement(projectPath = projectPath, modelName = modelName, processID = processID, format = thisFormat, "variableTypes")
                            names(variableTypes) <- getParameterFormatElement(projectPath = projectPath, modelName = modelName, processID = processID, format = thisFormat, "columnNames")
                            
                            #ord <- match(columnNames, names(functionParameters[[this]]))
                            #typesTable <- data.table::data.table(
                            #    columnNames = columnNames, 
                            #    variableTypes = variableTypes
                            #)[ord, ]
                            
                            
                            RstoxData::setColumnClasses(functionParameters[[this]], classes = variableTypes)
                                
                            
                            #
                            #
                            #
                            ## Set column classes:
                            #for(ind in seq_along(columnNames)) {
                            #    if(getRelevantClass(functionParameters[[this]][[ind]]) != variableTypes[ind]) {
                            #        thisColName <- columnNames[ind]
                            #        functionParameters[[this]][, (thisColName) := do.call(variableTypesFunctions[ind], )]
                            #    }
                            #}
                            
                            
                        }
                    }
                    # Set class to the defined class:
                    else if(classIsDefined && differingClass) {
                        class(functionParameters[[this]]) <- getRelevantClass(StoxFunctionParameterFormals[[this]])
                    }
                }
            }
        }
        else {
            stop("StoX: functionParameters must be a list")
        }
    }
    
    return(functionParameters)
}


formatProcessData <-  function(processData) {
    if(!is.list(processData)) {
        stop("StoX: ProcessData must be a list. The list can consist of objects of classes ", paste(getRstoxFrameworkDefinitions("outputTypes"), collapse = ","))
    }
    
    if(length(processData)) {
        processData <- mapply(formatProcessDataOne, processDataName = names(processData), processDataOne = processData, SIMPLIFY = FALSE)
    }
    
    return(processData)
}


formatProcessDataOne <-  function(processDataName, processDataOne) {
    
    if(!length(processDataOne)) {
        processDataOne <- data.table::data.table()
    }
    # Convert to sp:
    else if("features" %in% tolower(names(processDataOne))) {
        # Using geojsonsf instead of geojsonio to reduce the number of dependencies:
        #processDataOne <- geojsonio::geojson_sp(toJSON_Rstox(processDataOne, pretty = TRUE))
        
        # Check for empty multipolygon, which is not well treated by sf:
        StratumPolygon <- geojsonsf::geojson_sf(toJSON_Rstox(processDataOne, pretty = TRUE))
        
        if(length(StratumPolygon$geometry)) {
            
            # Set the assumed pojection:
            suppressWarnings(sf::st_crs(StratumPolygon) <- getRstoxBaseDefinitions("proj4string_longlat"))
            # Make sure that the StratumPolygon is a MULTIPOLYGON object:
            StratumPolygon <- sf::st_cast(StratumPolygon, "MULTIPOLYGON")
            
            # Add names:
            processDataOne <- RstoxBase::addStratumNames(StratumPolygon, accept.wrong.name.if.only.one = TRUE)
        }
        else {
            processDataOne <- getRstoxFrameworkDefinitions("emptyStratumPolygon")
        }
    }
    # If a data.table:
    else if(length(processDataOne) && data.table::is.data.table(processDataOne)) {
        
        convertStringToNA(processDataOne)
        ## Set numeric NAs:
        #jsonNA <- getRstoxFrameworkDefinitions("jsonNA")
        #decodeNumericNAOneProcessData(processDataOne, na = jsonNA)
        
        convertClassOfDataTable(processDataOne, getRstoxFrameworkDefinitions("processDataColumnTypes")[[processDataName]])
        
        convertToPosixInDataTable(processDataOne)
    }
    # Otherwise try to convert to data.table:
    else if(length(processDataOne) && is.convertableToTable(processDataOne)) {
        # Why was this extremely slow method used, where converting to and then from JSON slows things down imensely?:
        # processDataOne <- simplifyListReadFromJSON(processDataOne)
        # processDataOne <- data.table::as.data.table(processDataOne)
        
        # Convert to data.table:
        processDataOne <- data.table::rbindlist(processDataOne)
        
        convertStringToNA(processDataOne)
        ## Set numeric NAs:
        #jsonNA <- getRstoxFrameworkDefinitions("jsonNA")
        #decodeNumericNAOneProcessData(processDataOne, na = jsonNA)
        
        convertClassOfDataTable(processDataOne, getRstoxFrameworkDefinitions("processDataColumnTypes")[[processDataName]])
        
        convertToPosixInDataTable(processDataOne)
    }
    else {
        stop("StoX: ProcessData must be a list. The list can consist of objects of classes ", paste(getRstoxFrameworkDefinitions("outputTypes"), collapse = ","))
    }
    
    return(processDataOne)
}



simplifyListReadFromJSON <- function(x) {
    jsonlite::fromJSON(toJSON_Rstox(x), simplifyVector = TRUE)
}


convertStringToNA <- function(x) {
    chcols = names(x)[sapply(x, is.character)]
    #x[, (chcols) := lapply(.SD, replace, as.is=TRUE), .SDcols=chcols] # Changed to numeric when not intended
    x[, (chcols) := lapply(.SD, function(x) ifelse(x == "NA", NA, x)), .SDcols = chcols]
}


escapeTabAndNewline <- function(x) {
    chcols = names(x)[sapply(x, is.character)]
    #x[, (chcols) := lapply(.SD, replace, as.is=TRUE), .SDcols=chcols] # Changed to numeric when not intended
    if(length(chcols)) {
        x[, (chcols) := lapply(.SD, escapeNewLine), .SDcols = chcols]
        x[, (chcols) := lapply(.SD, escapeTab), .SDcols = chcols]
    }
}

escapeNewLine <- function(x) {
    gsub("\n", "\\n", x, fixed = TRUE)
}
escapeTab <- function(x) {
    gsub("\t", "\\t", x, fixed = TRUE)
}

#parseParameter <- function(parameter, simplifyVector = TRUE) {
#    # If the parameter is JSON, convert to list:
#    if("json" %in% class(parameter)) {
#        parameter <- jsonlite::fromJSON(parameter, simplifyVector = simplifyVector)
#    }
#    #else if(is.character(parameter) && jsonlite::validate(parameter)) {
#    # No need to validate, as inavlid json will lead to error in jsonlite::parse_json:
#    else if(is.character(parameter)) {
#        parameter <- jsonlite::parse_json(parameter, simplifyVector = simplifyVector)
#    }
#    parameter
#}

#' Function to parse a parameter coming from the GUI
#' 
#' @param parameter A JSON string holding the parameters.
#' @inheritParams jsonlite::fromJSON
#' 
#' @export
#' 
parseParameter <- function(parameter, simplifyVector = TRUE) {
    # If empty string, convert to NULL for non-character type:
    if(is.character(parameter) && nchar(parameter) == 0) {
        return(NULL)
    }
    
    
    if(is.list(parameter)) {
        return(parameter)
    }
    
    # Parse the JSON:
    out <- jsonlite::fromJSON(parameter, simplifyVector = simplifyVector)
    # If data.frame, convert to data.table:
    if(is.data.frame(out)) {
        out <- data.table::as.data.table(out)
    }
    return(out)
}




is.convertableToTable <- function(x, minLength = 1) {
    # If all elements of the list x are lists with equal length, x is convertable to data.table:
    length(x) && 
    is.list(x) && # The input must be a list
    all(sapply(x, is.list)) && # ... and a list of lists
    RstoxBase::allEqual(lengths(x)) && # ... and all must be of equal length
    all(lengths(x) >= minLength) && # ... and longer than 1
    !is.list(x[[1]][[1]]) # ... and finally, each list must not contain lists. We only check the first element here
}

is.convertableToVector <- function(x, minLength = 1) {
    length(x) && 
    is.list(x) && # The input must be a list
    !any(sapply(x, is.list)) && # ... and cannot be a list of lists
    all(lengths(x) == 1) # ... and all must have length 1
    !length(names(x)) # Convert to vector only if not named
}



getNewDefaultProcessName <- function(projectPath) {
    
    # Get all process names of the specified model:
    #processIndexTable <- readProcessIndexTable(projectPath, modelName)
    
    # Changed on 2021-01-19 to check against all processes of all models, as all process named should be unnique across models due to the possibility of gettinng output from processes in other models: 
    processIndexTable <- readProcessIndexTable(projectPath, modelName = NULL)
    processNames <- processIndexTable$processName
    
    getNewDefaultName(processNames, getRstoxFrameworkDefinitions("process_Prefix"))
}

getMaxProcessIntegerID <- function(projectPath) {
    # Get the file containing the maximum process integer ID:
    maxProcessIntegerIDFile <- getProjectPaths(projectPath, "maxProcessIntegerIDFile")
    
    # If missing, create the file as an empty file:
    if(!file.exists(maxProcessIntegerIDFile)) {
        maxProcessIntegerID <- 0
    }
    else {
        maxProcessIntegerID <- as.numeric(readLines(maxProcessIntegerIDFile, 1))
    }
    
    return(maxProcessIntegerID)
}

writeMaxProcessIntegerID <- function(projectPath, maxProcessIntegerID) {
    # Get the file containing the maximum process integer ID:
    maxProcessIntegerIDFile <- getProjectPaths(projectPath, "maxProcessIntegerIDFile")
    # Write the new maximum process integer ID:
    #data.table::fwrite(maxProcessIntegerIDTable, maxProcessIntegerIDFile, sep = "\t")
    writeLines(as.character(maxProcessIntegerID), maxProcessIntegerIDFile)
}




#createNewProcessID <- function(projectPath, modelName, n = 1) {
createNewProcessID <- function(projectPath, n = 1) {
        
    # Get the maximum  process integer ID:
    maxProcessIntegerID <- getMaxProcessIntegerID(projectPath)
    
    # Add 1 to the current process integer ID of the model
    processIntegerID <- maxProcessIntegerID + seq_len(n)
    maxProcessIntegerID <- max(processIntegerID)
    
    # Write the new maximum process integer ID:
    writeMaxProcessIntegerID(
        projectPath = projectPath, 
        maxProcessIntegerID = maxProcessIntegerID
    )
    
    # Create the processID and return this:
    createProcessIDString(processIntegerID)
}





createProcessIDString <- function(integerID) {
    # Create the processID and return this:
    numDigitsOfProcessIntegerID <- getRstoxFrameworkDefinitions("numDigitsOfProcessIntegerID")
    # Paste P to the process integer ID:
    if(length(integerID)) {
        processID <- paste0("P", formatC(integerID, width = numDigitsOfProcessIntegerID, format = "d", flag = "0"))
    }
    else {
        processID <- NULL
    }
    
    return(processID)
}



# Function to add an empty StoX process:
addEmptyProcess <- function(projectPath, modelName, processName = NULL, archive = TRUE, strict = TRUE) {
    
    # Get a default new process name, or check the validity of the given process name:
    if(length(processName)) {
        # Use warning and replacement of the process name here instead of error, as addProcess() is used to build a project from project description, and we want it to be able to open, only with modified process name, whereas a GUI uses no processName, but rather adds that afterwards:
        #validProcessName <- validateProcessName(
        #    projectPath = projectPath, 
        #    modelName = modelName, 
        #    newProcessName = processName, 
        #    strict = FALSE
        #    
        #)
        #if(!validProcessName) {
        #    warning("StoX: Process not added")
        #}
        
        processName <- checkProcessName(
            processName, 
            projectPath = projectPath, 
            strict = strict
        )
    }
    else {
        processName <- getNewDefaultProcessName(projectPath)
    }
    
    # Create an empty process:
    process <- createEmptyProcess(
        modelName = modelName, 
        processName = processName
    )
    
    # Get the process ID:
    processID <- createNewProcessID(
        projectPath = projectPath#, 
        #modelName = modelName
    )
    
    # Store the changes:
    setProcessMemory(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        process = process, 
        archive = archive
    )
    
    # Update the process index table:
    addToProcessIndexTable(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        processName = processName
    )
    
    # Return a data frame with process ID and name suited for appending to the process index table using addToProcessIndexTable():
    data.table::data.table(
        processID = processID, 
        processName = processName
    )
}

# Function to add all processes of template or project description:
addProcesses <- function(projectPath, projectMemory, returnProcessTable = TRUE, archive = TRUE, add.defaults = FALSE) {
    # Get the possible models:
    stoxModelNames <- getRstoxFrameworkDefinitions("stoxModelNames")
    
    # Loop through the possible models and add the processes:
    processes <- vector("list", length(stoxModelNames))
    names(processes) <- stoxModelNames
    
    for(modelName in stoxModelNames){
        for(ind in seq_along(projectMemory[[modelName]])){
            #processes [[modelName]] [[ind]] <- 
            temp <- addProcess(
                projectPath = projectPath, 
                modelName = modelName, 
                values = projectMemory[[modelName]][[ind]], 
                returnProcessTable = returnProcessTable, 
                archive = archive, 
                add.defaults = add.defaults, 
                strict = FALSE # We want to be able to open a project without error, and rather rename processes.
            )
        }
    }
    
    # Return the process table:
    if(returnProcessTable) {
        processTable <- getProcessTable(projectPath = projectPath, modelName = modelName)
        activeProcess <- getActiveProcess(projectPath = projectPath, modelName = modelName)
        return(
            list(
                processTable = processTable, 
                activeProcess = activeProcess, 
                saved = isSaved(projectPath)
            )
        )
    }
    else {
        return(TRUE)
    }
    
}


#' Add a StoX process to a model.
#' 
#' @inheritParams general_arguments
#' @inheritParams Projects
#' @inheritParams getProcessOutput
#' @param values A list of values to assign to the process, such as list(processName = "ReadBiotic", functionName = "RstoxBase::ReadBiotic").
#' @param returnProcessTable Logical: If TRUE return the process table.
#' @param add.defaults Logical: If TRUE defaults of a function are added when setting the function of the process.
#' @param strict Logical: If FALSE a proposed process name is changed to the default new process name, whereas TRUE throws an error.
#' 
#' @export
#' 
addProcess <- function(projectPath, modelName, values = NULL, returnProcessTable = TRUE, archive = TRUE, add.defaults = FALSE, strict = TRUE, afterProcessID =  NULL, beforeProcessID =  NULL) {
    
    # values must be a list:
    if(length(values) && !is.list(values)) {
        warning("StoX: Process not added. Values must be a list of specifics of the process.")
    }
    
    # Create an empty process:
    process <- addEmptyProcess(
        projectPath = projectPath, 
        modelName = modelName, 
        processName = values$processName, 
        archive = FALSE, 
        strict = strict
    )
    
    # Apply the arguments:
    valuesSansProcessName <- values[names(values) != "processName"]
    modifyProcess(
        projectPath = projectPath, 
        modelName = modelName, 
        processName = process$processName, 
        newValues = valuesSansProcessName, 
        archive = archive, 
        add.defaults = add.defaults
    )
    
    # Move the process if requested:
    if(length(beforeProcessID)) {
        afterProcessID <- getProcessIDByOffset(
            projectPath = projectPath,
            modelName = modelName,
            beforeProcessID, 
            offset = -1
        )
    }
    if(length(afterProcessID)) {
        rearrangeProcesses(
            projectPath = projectPath,
            modelName = modelName,
            processID = process$processID,
            afterProcessID = afterProcessID
        )
    }
        
    
    # Return the process:
    #process <- getProcessArguments(
    #    projectPath = projectPath, 
    #    modelName = modelName, 
    #    processID = process$processID
    #)
    
    # Set the status as not saved (saving is done when running a process):
    setSavedStatus(projectPath, status = FALSE)
    
    # Return the process table if requested:
    if(returnProcessTable) {
        processTable <- getProcessTable(projectPath = projectPath, modelName = modelName)
        activeProcess <- getActiveProcess(projectPath = projectPath, modelName = modelName)
        return(
            list(
                processTable = processTable, 
                activeProcess = activeProcess, 
                saved = isSaved(projectPath)
            )
        )
    }
    else {
        return(TRUE)
    }
}
#' Insert a StoX process after a specific process
#' 
#' @inheritParams general_arguments
#' @inheritParams addProcess
#' 
#' @export
#' 
expandProcess <- function(projectPath, modelName, processName, values = NULL, returnProcessTable = TRUE) {
    
    if(!length(values$processName)) {
        values$processName <- checkProcessName(
            paste0(processName, "_expand"), 
            projectPath, 
            strict = FALSE
        )
    }
    
    # Add an AddToStoxBiotic process which inputs the firstStoxBioticProcess 
    suppressWarnings(
        addProcess(
            projectPath = projectPath, 
            modelName = modelName, 
            values = values
        )
    )
    # Move the new process to immediately after the existing:
    processID_toMove <- getProcessIDFromProcessName(
        projectPath = projectPath, 
        modelName = modelName, 
        processName = values$processName
    )
    afterProcessID <- getProcessIDFromProcessName(
        projectPath = projectPath, 
        modelName = modelName, 
        processName = processName
    )
    rearrangeProcesses(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID_toMove$processID, 
        afterProcessID = afterProcessID$processID
    )
    
    # Modify all processes to using the new process as input:
    modifyProcessNameInFunctionInputs(
        projectPath = projectPath, 
        modelName = modelName, 
        processName = processName, 
        newProcessName = values$processName
    )
    
    # Set the status as not saved (saving is done when running a process):
    setSavedStatus(projectPath, status = FALSE)
    
    # Return the process table if requested:
    if(returnProcessTable) {
        processTable <- getProcessTable(projectPath = projectPath, modelName = modelName)
        activeProcess <- getActiveProcess(projectPath = projectPath, modelName = modelName)
        return(
            list(
                processTable = processTable, 
                activeProcess = activeProcess, 
                saved = isSaved(projectPath)
            )
        )
    }
    else {
        return(TRUE)
    }
}
    
#' Remove a StoX process
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
removeProcess <- function(projectPath, modelName, processID) {
    
    # Reset the model to the process just before the removed process:
    resetModel(projectPath = projectPath, modelName = modelName, processID = processID, shift = -1)
    
    # Update the project memory:
    removeProcessMemory(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Update the process index table:
    removeFromProcessIndexTable(projectPath = projectPath, modelName = modelName, processID = processID)
    
    # Set the status as not saved (saving is done when running a process):
    setSavedStatus(projectPath, status = FALSE)
    
    # Return the process table:
    processTable <- getProcessTable(projectPath = projectPath, modelName = modelName)
    activeProcess <- getActiveProcess(projectPath = projectPath, modelName = modelName)
    return(
        list(
            processTable = processTable, 
            activeProcess = activeProcess, 
            saved = isSaved(projectPath)
        )
    )
}


#' Duplicate a StoX process.
#' 
#' @inheritParams Projects
#' @inheritParams getProcessOutput
#' @param newProcessName The name of the new process. The default is the name of the process to copy added "_copy".
#' 
#' @export
#' 
duplicateProcess <- function(projectPath, modelName, processID, newProcessName = NULL) {
    
    # Get the process to copy:
    processToCopy <- getProcessArguments(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    
    # Set the name of the new process:
    if(length(newProcessName)) {
        processToCopy$processName <- newProcessName
    }
    else {
        processToCopy$processName <- paste(processToCopy$processName, "copy", sep = "_")
    }
    
    addProcess(
        projectPath = projectPath, 
        modelName = modelName, 
        values = processToCopy, 
        afterProcessID = processID
    )

}


#' Rearrange processes of a StoX model.
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
rearrangeProcesses <- function(projectPath, modelName, processID, afterProcessID = NULL) {
    # Rearrange the process index table defining the order of the processes:
    #if(length(afterProcessID)) {
    activeProcessID <- rearrangeProcessIndexTable(projectPath = projectPath, modelName = modelName, processID = processID, afterProcessID = afterProcessID)
    #}
    
    # Reset the model to the first of afterProcessID and the processes to be rearranged, but only if there was any change:
    if(length(activeProcessID)) {
        resetModel(projectPath = projectPath, modelName = modelName, processID = activeProcessID)
        # Set the status as not saved (saving is done when running a process):
        setSavedStatus(projectPath, status = FALSE)
    }
    
    # Return the process table:
    processTable <- getProcessTable(projectPath = projectPath, modelName = modelName)
    activeProcess <- getActiveProcess(projectPath = projectPath, modelName = modelName)
    return(
        list(
            processTable = processTable, 
            activeProcess = activeProcess, 
            saved = isSaved(projectPath)
        )
    )
}


isProcess <- function(x) {
    processProperties <- getRstoxFrameworkDefinitions("processProperties")
    is.list(x) && all(processProperties %in% names(x))
}


#### Functions to run models: ####
#' Run one process
#'
#' @inheritParams general_arguments
#' @inheritParams modifyProcess
#' @param saveProcessData Logical: If TRUE save the output as ProcessData, if the proecss is a ProcessData process.
#' @param returnProcessOutput Logical: If TRUE return the process output immediately after it is available. Used to get filter options.
#' @param fileOutput Logical: If TRUE save the output as a text file (or other format specified by the class or attributes of the output). If NULL (defafult) use the corresponding parameter of the process.
#' @param setUseProcessDataToTRUE Logical: If TRUE set the UseProcessData function parameter to TRUE in the process memory after execution, if the process is a ProcessData process.
#' @param replaceArgs A list of function parameters and inputs to override.
#' @param replaceData Either the data to replace the process output by, or a list of two elements \code{FunctionName} and \code{MoreArgs}, giving a function to apply to the output from the process with additional arguments stored in \code{MoreArgs}. The function is applied using \code{\link{do.call}}, with \code{args} being a list with the process output first, followed by the \code{MoreArgs}.
#' @param try Logical: If FALSE do not run the process in a \code{tryCatch}. Set this to FALSE when debugging, as the \code{tryCatch} masks the errors in the \code{traceback}.
#' 
#' 
#' @export
#' 
runProcess <- function(
    projectPath, modelName, processID, 
    msg = TRUE, msg.GUI = FALSE, 
    saveProcessData = TRUE, 
    returnProcessOutput = FALSE, fileOutput = NULL, setUseProcessDataToTRUE = TRUE, 
    purge.processData = FALSE, 
    replaceArgs = list(), replaceData = NULL,
    #output.file.type = c("default", "text", "RData", "rds"), # Not needed. This was maybe used in the early stages of the development.
    try = TRUE
) {
    
    # Get the model name:
    modelName <- getModel(modelName)
    
    # Stop if the stop file is present:
    stopFile <- getProjectPaths(projectPath, "stopFile")[[modelName]]
    if(file.exists(stopFile)) {
        stop("runProcesses() aborted by the user.")
    }
    
    if(msg) {
        startTime <- proc.time()[3]
    }
    
    # Get the function argument and the process info:
    functionArguments <- getFunctionArguments(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        replaceArgs = replaceArgs, 
        keepEmptyFunctionInputs = FALSE
    )
    
    # Jump out if nothing is returned, indicative of disabled process:
    if(!length(functionArguments)) {
        return(FALSE)
    }
    
    # Extract the process and the function arguments:
    process <- functionArguments$process
    functionArguments <- functionArguments$functionArguments
        
    # The fileOutput overrides the process parameter:
    if(!length(fileOutput)) {
        fileOutput <- process$processParameters$fileOutput
    }
    # If there is a parameter UseOutputData that is set to TRUE, do not write output and do not delete it prior to running the function:
    if(isTRUE(functionArguments$UseOutputData)) {
        fileOutput <- FALSE
    }

    # Try running the function, and return FALSE if failing:
    failed <- FALSE
    if(msg && !msg.GUI) {
        message(
            "StoX: Running ", modelName, " process ", 
            getProcessIndexFromProcessID(projectPath = projectPath, modelName = modelName, processID = processID), 
            ": ", 
            getProcessName(projectPath = projectPath, modelName = modelName, processID = processID), 
            "...", 
            appendLF = TRUE
        )
    }
    
    # Store the current active process ID:
    currentActiveProcessID <- getActiveProcess(projectPath = projectPath, modelName = modelName)$processID
    # Reset the model to the process just before the process to be run:
    if(!returnProcessOutput) {
        #resetModel(projectPath = projectPath, modelName = modelName, processID = processID, shift = -1, delete = c("memory", if(fileOutput) "text"), purgeOutputFiles = TRUE)
        # Changed to purgeOutputFiles = FALSE, as if TRUE Bootstrap output i deleted when this is changed and is the first process (in which case active proces becomes NA):
        resetModel(projectPath = projectPath, modelName = modelName, processID = processID, shift = -1, delete = c("memory", if(fileOutput) "text"), purgeOutputFiles = FALSE)
    }
    
    # Run the process:
    packageName <- getPackageNameFromPackageFunctionName(process$functionName)
    if(try) {
        processOutput <- tryCatch(
            do.call(
                getFunctionNameFromPackageFunctionName(process$functionName), 
                functionArguments, 
                envir = if(packageName == "RstoxFramework") environment() else as.environment(paste("package", packageName, sep = ":"))
            ), 
            error = function(err) {
                failed <<- TRUE
                stop(err)
            }
        )
    }
    else {
        processOutput <- do.call(
            getFunctionNameFromPackageFunctionName(process$functionName), 
            functionArguments, 
            envir = if(packageName == "RstoxFramework") environment() else as.environment(paste("package", packageName, sep = ":"))
        )
    }
    
    
    # Apply the replaceData, which can be a function with first parameter the processOutput and additional parameters given in ..., or an actual object to replace the output by:
    #thisReplaceData <- replaceData[[process$processName]]
    if(length(replaceData) && is.character(replaceData)) {
        replaceData <- list(FunctionName = replaceData)
    }
    if(is.list(replaceData) && !data.table::is.data.table(replaceData) && is.character(replaceData$FunctionName)) {
        
        #if(!exists(replaceData$FunctionName)) {
        #    stop("If replaceData is given as a list with a function name first, this must be an existing function (was ", replaceData$FunctionName, ").")
        #}
        args <- c(
            structure(
                list(processOutput), 
                names = getStoxFunctionMetaData(process$functionName, "functionOutputDataType")
            ), 
            functionArguments, 
            replaceData$MoreArgs
        )
        # For functions that return the same data type as used as input, there will be equally named elements, so we only keep the new:
        if(any(duplicated(names(args)))) {
            args <- args[!duplicated(names(args))]
        }
        # Unname the first element to support arbitraty name of the first argument in the  replaceData$FunctionName:
        names(args)[1] <- ""
        
        # Run the replaceData function. Here we are using the full address of the function, as the do.call_robust has been relocacted to RstoxData. This should be changed to using the full address in the first place in the BootstrapMethodTable in the future, if we want to allow custom specifications of bootstrapping:
        #replaceData$FunctionName <- paste("RstoxFramework", replaceData$FunctionName, sep = "::")
        processOutput <- RstoxData::do.call_robust(
        #processOutput <- do.call(
            what = replaceData$FunctionName, 
            args = args, 
            # This allows for arbitrarily named first argument in the function (FunctionName):
            keep.unnamed = TRUE
        )
    }
    else if(length(replaceData)) {
        processOutput <- replaceData
    }
    
    if(failed){
        return(FALSE)
    }
    # If the processOutput has length (or is an empty SpatialPolygonsDataFrame) or empty data.table:
    else if(length(processOutput) || any(c("data.table", "sf") %in% getRelevantClass(processOutput))){
        
        
        # Update the active process ID:
        writeActiveProcessID(
            projectPath = projectPath, 
            modelName = modelName, 
            activeProcessID = processID, 
            affectLaterModels = TRUE, 
            currentActiveProcessID = currentActiveProcessID, 
            processDirty = FALSE
        )

        # If a valid output class, wrap the function output to a list named with the data type (but not for BootstrapData, which is a list of valid output classes AND a valid output class itself):
        if(isValidOutputDataClass(processOutput) && !isBootstrapFunction(process$functionName)) {
            processOutput <- list(processOutput)
            names(processOutput) <- getStoxFunctionMetaData(process$functionName, "functionOutputDataType")
        }
        
        # Set the precision of the processOutput:
        processOutput <- setRstoxPrecision(processOutput)
        
        # Return the process output if requested:
        if(returnProcessOutput) {
            return(processOutput)
        }
        
        # Store the processData:
        if(saveProcessData && isProcessDataFunction(process$functionName)) {
            modifyProcessData(projectPath = projectPath, modelName = modelName, processID = processID, newProcessData = processOutput, purge.processData = purge.processData)
            
            # Set the function parameters UseProcessData to TRUE:
            if(setUseProcessDataToTRUE) {
                setUseProcessData(projectPath = projectPath, modelName = modelName, processID = processID)
            }
        }
        else {
            # Set the propertyDirty flag to FALSE, as no change has been made to the UseProcessData flag:
            writeActiveProcessID(projectPath, modelName, propertyDirty = FALSE)
        }
        
        
        # Write to memory files:
        processOutput <- writeProcessOutputMemoryFiles(processOutput = processOutput, projectPath = projectPath, modelName = modelName, processID = process$processID)
        
        # Write to text files:
        # Use fileOutput if given and process$processParameters$fileOutput otherwise to determine whether to write the output to the output.file.type:
        if(fileOutput) {
            # Escape strings when writing to text files:
            writeProcessOutputTextFile(processOutput = processOutput, projectPath = projectPath, modelName = modelName, processID = process$processID, escape = TRUE)
        }
        
        # Add info of the time spent:
        if(msg) {
            timeSpent <- proc.time()[3] - startTime
            message(
                    "StoX: (time used: ", round(timeSpent, digits = 3), " s)"
            )
        }
        
        #invisible(processOutput)
        TRUE
    }
}


setUseProcessData <- function(projectPath, modelName, processID, UseProcessData = TRUE) {
    # Try setting UseProcessData to TRUE:
    modified <- modifyFunctionParameters(projectPath = projectPath, modelName = modelName, processID = processID, newFunctionParameters = list(UseProcessData = UseProcessData))
    # If modified, set propertyDirty to TRUE:
    if(modified) {
        writeActiveProcessID(projectPath, modelName, propertyDirty = TRUE) 
    }
}



getFunctionArguments <- function(projectPath, modelName, processID, arguments = NULL, replaceArgs = list(), keepEmptyFunctionInputs = TRUE) {
    
    # Get the process:
    process <- getProcessArguments(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        #only.valid = TRUE
        only.valid = FALSE
    )
    
    # Check that the function name is given:
    if(length(process$functionName) == 0 || nchar(process$functionName) == 0) {
        processName <- getProcessNameFromProcessID(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID
        )
        stop("The process ", processName, " does not specify a function name.")
    }
    
    # If not not enabled, return immediately:
    if(!process$processParameters$enabled) {
        return(NULL)
    }
    
    # Build a list of the arguments to the function:
    functionArguments <- list()
    
    # Add the processData if a processData function. This must be added after dropping one level if a list of one list:
    if(isProcessDataFunction(process$functionName)) {
        functionArguments$processData <- process$processData
        if(is.listOfOneList(functionArguments$processData)) {
            functionArguments$processData <- functionArguments$processData[[1]]
        }
    }
    
    # Add the projectPath and outputData file path if a bootstrap function (return only the outputData file path, and then open the file in Bootstrap() if needed):
    if(isBootstrapFunction(process$functionName)) {
        # Set the projectPath:
        functionArguments$projectPath <- projectPath
        
        # Get and read any bootstrap file from before:
        functionArguments["outputData"] <- getProcessOutputTextFilePath(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = process$processID, 
            processOutput = NULL, 
            file.ext = "RData"
        )
    }
    else if(isBootstrapNetCDF4Function(process$functionName)) {
        # Set the projectPath:
        functionArguments$projectPath <- projectPath
        
        # Get and read any bootstrap file from before:
        functionArguments["outputData"] <- getProcessOutputTextFilePath(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = process$processID, 
            processOutput = NULL, 
            file.ext = "nc"
        )
        
        # Get the memory file to copy the previous output file to if running BootstrapNetCDF4():
        folderPath <- getProcessOutputFolder(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = process$processID, 
            type = "memory"
        )
        dataType <- getStoxFunctionMetaData(process$functionName, "functionOutputDataType")
        functionArguments["outputMemoryFile"] <- file.path(folderPath, paste(dataType, "nc", sep = "."))
    }
    
    # Add functionInputs and functionParameters:
    functionArguments <- c(
        functionArguments, 
        # Discard empty function inputs if keepEmptyFunctionInputs. This enables the "argument ___ is missing, with no default" error in R:
        if(keepEmptyFunctionInputs) process$functionInputs else process$functionInputs[lengths(process$functionInputs) > 0], 
        process$functionParameters
    )
    
    # Insert any arguments in replaceArgs:
    namesOfReplaceArgsToInsert <- intersect(names(replaceArgs), names(functionArguments))
    namesOfReplaceArgsNotToInsert <- setdiff(names(replaceArgs), names(functionArguments))
    
    if(length(namesOfReplaceArgsNotToInsert)) {
        warning("The replaceArgs/replaceArgsList contains the following parameters that do not exist in the process: ", paste(namesOfReplaceArgsNotToInsert, sep = ", "))
    }
    if(length(namesOfReplaceArgsToInsert)) {
        functionArguments[namesOfReplaceArgsToInsert] <- replaceArgs[namesOfReplaceArgsToInsert]
    }
    
    # Get absolute paths:
    functionArguments <- getAbsolutePaths(
        functionParameters = functionArguments, 
        projectPath = projectPath, 
        modelName = modelName,
        processID = processID
    )
    
    
    # Keep only arguments to show, but for a processData process only if UseProcessData is set to FALSE:
    if(! "UseProcessData" %in% namesOfReplaceArgsToInsert || isTRUE(replaceArgs$UseProcessData)) {
        functionArguments <- extractArgumentsToShow(arguments = functionArguments, projectPath = projectPath, modelName = modelName, processID = processID, argumentFilePaths = NULL) # Using NULL here, as argumentFilePaths has not been read. Should it?
    }
    
    
    # Get the function input as output from the previously run processes:
    functionInputNames <- intersect(names(functionArguments), names(process$functionInputs))
    # Also, remove empty function inputs (added on 2020-11-19):
    functionInputNames <- functionInputNames[lengths(functionArguments[functionInputNames]) > 0L]
    
    functionInputProcessNames <- unlist(functionArguments[functionInputNames])
    
    # Add the function input data:
    functionInputData <- getFunctionInputData(
        functionInputProcessNames = functionInputProcessNames, 
        projectPath = projectPath
    ) 
    
    if(length(functionInputData)) {
        # Get the actual function inputs from the functionInputsProcessIDs:
        functionArguments[functionInputNames] <- functionInputData
    }
    
    
    return(
        list(
            functionArguments = functionArguments, 
            process = process
        )
    )
    functionArguments
}




getFunctionInputData <- function(functionInputProcessNames, projectPath, strict = TRUE) {
    
    if(length(functionInputProcessNames)) {
        # Get the function input process IDs (returned as a data.table due to the rbindlist()):
        functionInputsProcessIDs <- data.table::rbindlist(mapply(
            getProcessIDFromProcessName, 
            projectPath = projectPath, 
            processName = functionInputProcessNames, 
            MoreArgs = list(
                modelName = NULL
            ), 
            SIMPLIFY = FALSE
        ))
        
        functionInputData <- structure(vector("list", length(functionInputProcessNames)), names = names(functionInputProcessNames))
            
        if(strict && nrow(functionInputsProcessIDs) != length(functionInputProcessNames)) {
            stop("Some function inputs are not specified: ", paste(setdiff(functionInputProcessNames, names(functionInputsProcessIDs)), collapse = ", "))
            
        }
        
        # Get the actual function inputs from the functionInputsProcessIDs:
        presentFunctionInputData <- mapply(
            getProcessOutput, 
            projectPath = projectPath, 
            modelName = functionInputsProcessIDs$modelName, 
            processID = functionInputsProcessIDs$processID, 
            SIMPLIFY = FALSE, 
            warn = strict
        )
        
        if(length(presentFunctionInputData)) {
            functionInputData[names(functionInputProcessNames)] <- presentFunctionInputData
            #names(functionInputData) <- functionInputProcessNames
        }
        
        return(functionInputData)
    }
    else {
        return(list())
    }
}


##################################################
##################################################
#' Get output of a StoX process.
#' 
#' Gets the output of a process that has been run.
#' 
#' @inheritParams fixedWidthTable
#' @inheritParams readProcessOutputFile
#' @inheritParams getProcessOutputFiles
#' @inheritParams general_arguments
#' @param tableName The name of the table to extract from the process.
#' @param geoJsonName The name of the GeoJSON object to extract from the process.
#' @param subFolder If the process returns subfolders (ReadBiotic and ReadAcoustic, where the subfolders represent files), specify the name of the folder with this parameter.
#' @param drop Logical: If TRUE drop the list if only one element.
#' @param drop.datatype Logical: If TRUE drop the top level of the output if in a list, which is the level named by the data type.
#' 
#' @export
#' 
getProcessOutput <- function(projectPath, modelName, processID, tableName = NULL, subFolder = NULL, flatten = FALSE, pretty = FALSE, pretty.json = FALSE, pageindex = integer(0), linesPerPage = 1000L, columnSeparator = " ", lineSeparator = NULL, na = "-", enable.auto_unbox = TRUE, drop = FALSE, drop.datatype = TRUE, splitGeoJson = TRUE, warn = TRUE) {
    
    # If the 'tableName' contains "/", extract the 'subFolder' and 'tableName':
    if(any(grepl("/", tableName))) {
        subFolder_tableName <- strsplit(tableName, "/")
        subFolder <- sapply(subFolder_tableName, "[", 1)
        tableName <- sapply(subFolder_tableName, "[", 2)
    }
   
    # Get the files 
    processOutputFiles <- getProcessOutputFiles(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        warn = warn
    )
    if(!length(processOutputFiles)) {
        return(NULL)
    }
    
    # Get the directory holding the output files:
    folderPath <- getProcessOutputFolder(projectPath = projectPath, modelName = modelName, processID = processID, type = "memory")
    # Detect whether the output is a list of tables (depth 1) or a list of lists of tables (depth 2):
    folderDepth <- getFolderDepth(folderPath)
    # Get the file paths of the requested memory files:
    if(folderDepth == 1) {
        # Get the selected tables:
        if(length(tableName)) {
            processOutputFiles <- selectValidElements(processOutputFiles, tableName)
        }
    }
    else {
        # Apply the subFolder if given:
        if(length(subFolder)) {
            processOutputFiles <- selectValidElements(processOutputFiles, subFolder)
        }
        
        # Also select the tables of each sub folder:
        if(length(tableName)) {
            # Warning: This selection ignores the file extension by the partial matching of R:
            #processOutputFiles <- lapply(processOutputFiles, selectValidElements, tableName)
            
            # Make sure the tableName is present among the files:
            for(ind in seq_along(processOutputFiles)) {
                temp <- selectValidElements(processOutputFiles[[ind]], tableName)
                if(length(temp) || names(processOutputFiles)[ind] != tableName) {
                    processOutputFiles[[ind]] <- temp
                }
            }
            
            processOutputFiles <- processOutputFiles[lengths(processOutputFiles) > 0]
        }
    }
    
    if(length(processOutputFiles) == 0) {
        warning("StoX: Invalid specification of projectPath, modelName, processID or tableName (most likely tableName).")
    }
    
    
    # Read the files recursively:
    processOutput <- rapply(
        processOutputFiles, 
        readProcessOutputFile, 
        flatten = flatten, 
        pretty = pretty, 
        pretty.json = pretty.json, 
        linesPerPage = linesPerPage, 
        pageindex = pageindex, 
        columnSeparator = columnSeparator, 
        lineSeparator = lineSeparator, 
        na = na, 
        enable.auto_unbox = enable.auto_unbox, 
        how = "replace", 
        splitGeoJson = splitGeoJson
    )

    # Add data type as attribute if the file is present (currentlly only for boostrap):
    dataTypeFileName <- file.path(folderPath, "dataType.txt")
    if(is.list(processOutput) && file.exists(dataTypeFileName)) {
        dataType <- data.table::fread(dataTypeFileName)
        for(name in names(processOutput)) {
            attr(processOutput[[name]], "dataType") <- dataType[name == processName, dataType]
        }
    }
    
    # Unlist the top level if a single tabled data type is wrapped in a list:
    if(drop.datatype && is.list(processOutput) && length(processOutput) == 1 && names(processOutput) %in% getRstoxFrameworkDefinitions("stoxDataTypes")$functionOutputDataType) {
        processOutput <- processOutput[[1]]
    }
    
    # Unlist if only one element:
    if(drop) {
        #while(is.list(processOutput) && !data.table::is.data.table(processOutput) && length(processOutput) == 1) {
        while(is.listOfOneList(processOutput)) {
            processOutput <- processOutput[[1]]
        }
    }
    
    ### # If the output has length 1 and has names, create a two element vector with the name first and the data second, so that this will show in the GUI:
    ### if(pretty && length(processOutput) == 1 && length(names(processOutput)) == 1) {
    ###     processOutput <- c(names(processOutput), processOutput)
    ### }
    
    return(processOutput)
}
#' 
#' @rdname getProcessOutput
#' @export
#' 
getProcessTableOutput <- function(projectPath, modelName, processID, tableName = NULL, flatten = FALSE, pretty = FALSE, pageindex = integer(0), linesPerPage = 1000L, columnSeparator = " ", na = "-", drop = FALSE) {
    getProcessOutput(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        tableName = tableName, 
        flatten = flatten, 
        pretty = pretty, 
        pageindex = pageindex, 
        linesPerPage = linesPerPage, 
        columnSeparator = columnSeparator, 
        na = na, 
        drop = drop, 
        warn = FALSE
    )
}
#' 
#' @rdname getProcessOutput
#' @export
#' 
getProcessGeoJsonOutput <- function(projectPath, modelName, processID, geoJsonName = NULL, pretty = FALSE, splitGeoJson = TRUE) {
    getProcessOutput(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        tableName = geoJsonName, 
        pretty = TRUE, # Whether to return a list with data, numberOfLines and numberOfPages.
        pretty.json = pretty, 
        splitGeoJson = splitGeoJson, 
        warn = FALSE
    )
}


##################################################
##################################################
#' Get output of a StoX process.
#' 
#' Gets the output of a process that has been run.
#' 
#' @inheritParams general_arguments
#' @param plotName The name of the plot.
#' 
#' @export
#' 
getProcessPlotOutput <- function(projectPath, modelName, processID, plotName = NULL) {
    
    # Get the files 
    processOutputFiles <- getProcessOutputFiles(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        warn = FALSE
    )
    if(!length(processOutputFiles)) {
        return(NULL)
    }
    
    if(length(plotName)) {
        processOutputFiles <- selectValidElements(processOutputFiles, plotName)
    }
    
    # Read the files recursively:
    processOutput <- rapply(
        processOutputFiles, 
        readMemoryFile, 
        how = "replace"
    )
    
    # Define temp file paths:
    tempFileNames <-  paste(basename(tools::file_path_sans_ext(processOutputFiles)), "png", sep = ".")
    tempFilePaths <- file.path(tempdir(), tempFileNames)
    
    # The file paths are updated in ggsaveApplyDefaults (the GUI expects png):
    tempFilePaths <- mapply(ggsaveApplyDefaults, processOutput, tempFilePaths, MoreArgs = list(overrideAttributes = list(Format = "png")))

    return(tempFilePaths)
}


is.listOfOneList <- function(x) {
    is.list(x)      && !data.table::is.data.table(x) && length(x) == 1 && 
    is.list(x[[1]]) && !data.table::is.data.table(x[[1]])
}

unlistProcessOutput <- function(processOutput) {
    if(is.list(processOutput[[1]]) && !data.table::is.data.table(processOutput[[1]])) {
        names1 <- names(processOutput)
        names2 <- lapply(processOutput, names)
        processOutput <- unlist(processOutput, recursive = FALSE)
        names(processOutput) <- paste(rep(names1, lengths(names2)), unlist(names2), sep = "/")
    }
    return(processOutput)
}


#' Get output data from processes of a StoX model
#' 
#' @inheritParams general_arguments
#' @param drop.datatype Logical: If TRUE, drop the top level of the output list if it has length 1 and that single element is named by the datatype name.
#' @param unlistDepth2 Logical: Related to \code{drop.datatype}, but setting this to TRUE unlists output data that are nested in 2 levels, such as output from \code{\link[RstoxData]{ReadBiotic}}, which outputs a set of tables for each input file. Using unlistDepth2 = TRUE puts all these tables in one list, and uses the concatenation of the file names and the table name separated by underscore. This is how it is displayed in the StoX GUI when selecting "View output".
#' 
#' @export
#' 
getModelData <- function(projectPath, modelName, processes = NULL, startProcess = 1, endProcess = Inf, drop.datatype = TRUE, warn = TRUE, unlistDepth2 = FALSE) {
    
    # Get the processes to get output from, either specified with the 'processes' argument or the 'startProcess' and 'endProcess' arguments:
    processTable <- readProcessIndexTable(
        projectPath = projectPath, 
        modelName = modelName, 
        processes = processes, 
        startProcess = startProcess, 
        endProcess = endProcess, 
        warn = warn
    )
    
    if(nrow(processTable)) {
        # Get the process outputs:
        processOutput <- mapply(
            getProcessOutput, 
            processID = processTable$processID, 
            MoreArgs = list(
                projectPath = projectPath, 
                modelName = modelName, 
                drop.datatype = drop.datatype
            ), 
            SIMPLIFY = FALSE
        )
        names(processOutput) <- processTable$processName
    }
    else {
        processOutput <- NULL
    }
    
    if(unlistDepth2) {
        processOutput <- lapply(processOutput, unlistToDataType)
    }
    
    return(processOutput)
}


#' Function to read a single process output file, possibly by pages and in flattened and pretty view:
#' 
#' @inheritParams fixedWidthTable
#' @param filePath The file path of the process output file to read.
#' @param flatten Logical: Should the output tables that contain cells of length > 1 be expanded to that the other columns are repeated, resulting in a regular table.
#' @param pretty Logical: If TRUE pad with space in each cell to the maximum number of characters of the column including header.
#' @param pretty.json Logical: If TRUE prettify the geojson.
#' @param pageindex A vector of the pages to return with \code{linesPerPage} number of lines (rows). Default is to not split into pages.
#' @param linesPerPage The number of lines per page if \code{pageindex} is given.
#' @param splitGeoJson Logical: If TRUE split the geojson into a vector of separate lines.
#' 
readProcessOutputFile <- function(filePath, flatten = FALSE, pretty = FALSE, pretty.json = TRUE, pageindex = integer(0), linesPerPage = 1000L, columnSeparator = " ", lineSeparator = NULL, na = "-", enable.auto_unbox = FALSE, splitGeoJson = TRUE) {
    
    
    # Read the process output file:
    data <- readMemoryFile(filePath)
    
    # Check whether the table is rugged:
    if(flatten && data.table::is.data.table(data) && isDataTableRugged(data)) {
        data <- flattenDataTable(data)
    }
    
    if(pretty) {
        # If a SpatialPolygonsDataFrame, prettify and convert to character
        #if(getRelevantClass(data) == "SpatialPolygonsDataFrame") {
        if(getRelevantClass(data) == "sf") {
            #geojsonio::geojson_json(processOutput, pretty = TRUE)
            #data <- jsonlite::prettify(geojsonsf::sf_geojson(sf::st_as_sf(data)))
            data <- replaceSpatialFileReference(
                buildSpatialFileReferenceString(
                    data
                )
            )
            
            if(pretty.json || splitGeoJson) {
                data <- jsonlite::prettify(data)
            }
            
            # We need to split into a vector of lines:
            if(splitGeoJson) {
                data <- strsplit(data, "\n", fixed = TRUE)[[1]]
                # Extract the requested lines:
                numberOfLines <- length(data)
                numberOfPages <- ceiling(numberOfLines / linesPerPage)
            }
            else {
                data <- as.character(data)
                numberOfLines <- 1
                numberOfPages <- 1
            }
            
            data <- list(
                data = data, 
                numberOfLines = numberOfLines, 
                numberOfPages = numberOfPages
            )
        }
        # If a table, allow additional options:
        else if(data.table::is.data.table(data) || is.matrix(data)) {
            
            # Extract the requested lines:
            numberOfLines <- nrow(data)
            numberOfPages <- ceiling(numberOfLines / linesPerPage)
            if(length(pageindex)) {
                linesToExtract <- seq_len(linesPerPage) + rep((pageindex - 1) * linesPerPage, each = linesPerPage)
                linesToExtract <- linesToExtract[linesToExtract <= numberOfLines]
                data <- data[linesToExtract, ]
            }
            
            # Convert to pretty view, which inserts spaces to obtain 
            data <- fixedWidthTable(
                data, 
                columnSeparator = columnSeparator, 
                lineSeparator = lineSeparator, 
                na = na, 
                enable.auto_unbox = enable.auto_unbox
            )
            
            # Add a line "... truncated" if the page is not first and not the last:
            if(pageindex > 1) {
                data <- c(
                    "... truncated", 
                    data
                )
            }
            if(pageindex < numberOfPages) {
                data <- c(
                    data,
                    "... truncated"
                )
            }
            
            
                
            # In the pretty model, output a list containing the number of lines and the number of pages:
            data <- list(
                data = data, 
                numberOfLines = numberOfLines, 
                numberOfPages = numberOfPages
            )
        }
        else {
            # Add numberOfLines = 0 and numberOfPages = 0 to conform to the output used for tables in the GUI:
            if(length(data)) {
                
                # The GUI need this data with no StoX class (as BootstrapNetCDF4 is a class)
                data <- as.character(data)
                
                # Extract the requested lines:
                numberOfLines <- length(data)
                numberOfPages <- ceiling(numberOfLines / linesPerPage)
                
                data <- list(
                    data = data, 
                    numberOfLines = numberOfLines, 
                    numberOfPages = numberOfPages
                )
            }
            else {
                data <- list(
                    data = list(), 
                    numberOfLines = 0, 
                    numberOfPages = 0
                )
            }
        }
    }
    
    
    return(data)
}



flattenProcessOutput <- function(processOutput) {
    #if(getRelevantClass(processOutput) == "SpatialPolygons") {
    if(getRelevantClass(processOutput) == "sf") {
        #geojsonio::geojson_json(processOutput, pretty = TRUE)
        jsonlite::prettify(geojsonsf::sf_geojson(processOutput))
    }
    else if(getRelevantClass(processOutput) == "data.table") {
        # Check whether the table is rugged:
        if(isDataTableRugged(processOutput)) {
            flattenDataTable(processOutput)
        }
    }
    else if(getRelevantClass(processOutput) %in% c("matrix", "character")) {
        processOutput
    }
    else {
        stop("Invalid process output.")
    }
}

#' Function to get all process output memory files of a process:
#' 
#' @inheritParams general_arguments
#' @param onlyTableNames Logical: If TRUE return only table names.
#' @param type One of c("memory", "output", "text".
#' @param warn Logical: If TRUE warn if the process has not bee run.
#' @export
#' 
getProcessOutputFiles <- function(projectPath, modelName, processID, onlyTableNames = FALSE, type = "memory", warn = TRUE) {
    
    # Get the directory holding the output files:
    folderPath <- getProcessOutputFolder(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        type = type
    )
    
    if(length(processID) > 1) {
        stop("processID must have length 1 (was ", length(processID), ")")
    }
    
    # If the folder does not exist, it is a sign that the process does not exist:
    if(length(folderPath) == 0 || !file.exists(folderPath)) {
        #processName <- getProcessName(projectPath, modelName, processID)
        #stop("Has the previous processes been run? The folder ", folderPath, " does not exist.")
        if(warn) {
            warning("StoX: Process ", getProcessNameFromProcessID(projectPath = projectPath, modelName = modelName, processID = processID), " of the model ", modelName, " has not been run.")
        }
        
        return(NULL)
    }
    
    ## Detect whether the output is a list of tables (depth 1) or a list of lists of tables (depth 2):
    #folderDepth <- getFolderDepth(folderPath)
    
    # Get the file paths of the memory files and prepare the processOutput for writing to these files:
    processOutputFiles <- getFilesRecursiveWithOrder(folderPath)
    #if(folderDepth == 1) {
    #    processOutputFiles <- listMemoryFiles(folderPath)
    #}
    #else {
    #    # Get the sub folder paths and create the folders:
    #    folderPaths <- list.dirs(folderPath, recursive = FALSE)
    #    processOutputFiles <- lapply(folderPaths, listMemoryFiles)
    #    names(processOutputFiles) <- basename(folderPaths)
    #}
    
    if(onlyTableNames) {
        # Strip to only the table names of the folderPath:
        # Added fixed = TRUE, since special characters cause problems:
        processOutputFiles <- gsub(path.expand(folderPath), "", unname(unlist(processOutputFiles)), fixed = TRUE)
        # Remove the resulting trailing "/" and the file extension:
        processOutputFiles <- substring(processOutputFiles, 2)
        processOutputFiles <- tools::file_path_sans_ext(processOutputFiles)
    }
    
    processOutputFiles
}


# Function to get the file paths of the memory files recursively:
getFilesRecursiveWithOrder <- function(folderPath) {
    dirs <- list.dirs(folderPath, recursive = FALSE)
    if(length(dirs)) {
        output <- lapply(dirs, getFilesRecursiveWithOrder)
        names(output) <- basename(dirs)
        output <- c(
            listMemoryFiles(folderPath),
            output
        )
    }
    else {
        output <- listMemoryFiles(folderPath)
    }
    
    return(output)
}

# Function to list RDS file in a folder:
listMemoryFiles <- function(folderPath) {
    # Create a list of the files, and name it with the file names sans ext representing the output name:
    ext <- getRstoxFrameworkDefinitions("allMemoryFileFormats")
    extPattern <- paste0("\\.", ext, "$", collapse = "|")
    #out <- as.list(list.files(folderPath, full.names = TRUE, pattern = "\\.rds$"))
    out <- as.list(list.files(folderPath, full.names = TRUE, pattern = extPattern))
    names(out) <- basename(tools::file_path_sans_ext(unlist(out)))
    
    # Read the order file if present:
    orderFile <- file.path(folderPath, "tableOrder.txt")
    ##orderFile <- file.path(folderPath, "tableOrder.rds")
    if(file.exists(orderFile)) {
        tableOrder <- readLines(orderFile)
        #tableOrder <- readRDS(orderFile)
        tableOrder <- basename(tools::file_path_sans_ext(unlist(tableOrder)))
        out <- out[tableOrder]
    }
    
    out
}



#' Get the names of the output tables of a process
#' 
#' @inheritParams general_arguments
#' @export
#' 
getProcessOutputTableNames <- function(projectPath, modelName, processID) {
    # Get the output file names, and add the process name:
    tableNames <- getProcessOutputFiles(projectPath = projectPath, modelName = modelName, processID = processID, onlyTableNames = TRUE)
    ### processName <- getProcessName(projectPath, modelName, processID)
    #tableNames <- paste(processName, tableNames, sep ="_")
    
    # Ensure that this is a vector in JSON after auto_unbox = TRUE, by using as.list():
    tableNames <- as.list(tableNames)
    return(tableNames)
}


#' Get the names of the output tables of a process
#' 
#' @inheritParams general_arguments
#' @export
#' 
getProcessOutputElements <- function(projectPath, modelName, processID) {
    # Get the output file names, and add the process name:
    elementName <- getProcessOutputFiles(projectPath = projectPath, modelName = modelName, processID = processID, onlyTableNames = TRUE, warn = FALSE)
    
    if(!length(elementName)) {
        return(NULL)
    }
    
    processName <- getProcessNameFromProcessID(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    elementFullName <- paste0(processName, "(", elementName, ")")
    
    
    # Get the element types:
    outputClass <- readOutputClass(projectPath, modelName, processID)
    elementType <- getOutputElementType(outputClass)
    
    elementInfo <- data.table::data.table(
        elementName = elementName, 
        elementFullName = elementFullName, 
        elementType = elementType
    )
    
    return(elementInfo)
}


deleteProcessOutput <- function(projectPath, modelName, processID, type = c("memory", "output")) {
    # Get the directory holding the output files:
    folderPath <- getProcessOutputFolder(projectPath = projectPath, modelName = modelName, processID = processID, type = type)
    unlink(folderPath, recursive = FALSE, force = TRUE)
}



#' Find the output folder of a process 
#' 
#' @inheritParams general_arguments
#' @inheritParams getProcessOutput
#' @param type The type of output folder, one of "memory", to point to the memory output (files that only live while the project in open), or "output" or "text to point to the output folder holding files that continue living after the project is closed (written when Write output to file is checked in the GUI).
#' @export
#' 
getProcessOutputFolder <- function(projectPath, modelName, processID, type = c("memory", "output", "text"), subFolder = NULL) {
    type <- RstoxData::match_arg_informative(type)
    if(type == "memory") {
        folderPath <- file.path(getProjectPaths(projectPath, "dataModelsFolder"), modelName, processID)
    }
    else if(type %in% c("output", "text")) {
        # Get the processName and build the folderPath:
        processName <- getProcessNameFromProcessID(
            projectPath = projectPath, 
            modelName = modelName, 
            processID = processID
        )
        folderPath <- file.path(
            getProjectPaths(
                projectPath = projectPath, 
                name = modelName
            ), 
            processName
        )
        # Add subFolder:
        if(length(subFolder)) {
            folderPath <- file.path(folderPath, subFolder)
        }
    }
    else {
        stop("typetype must be one of \"memory\" and \"output\"/\"text\"")
    }
    return(folderPath)
}

#' Function to get process ID from process name
#' 
#' @inheritParams general_arguments
#' @param only.processID Logical: If TRUE return only the processID (not a table of processName and processID).
#' 
#' @export
#' 
getProcessIDFromProcessName <- function(projectPath, modelName, processName, only.processID = FALSE) {
    # Get the table linking process names and IDs:
    processIndexTable <- readProcessIndexTable(
        projectPath = projectPath, 
        modelName = modelName
    )
    # Extract the requested process ID:
    validRow <- processIndexTable$processName == processName
    if(only.processID) {
        processIndexTable[validRow, processID]
    }
    else {
        processIndexTable[validRow, ]
    }
}


#' Function to get process index from process ID
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
getProcessIndexFromProcessID <- function(projectPath, modelName, processID) {
    # Get the table linking process names and IDs:
    processIndexTable <- readProcessIndexTable(
        projectPath = projectPath, 
        modelName = modelName
    )
    processIndex <- which(processIndexTable$processID == processID)
    # Added 0 as output for processes that has not been run:
    if(!length(processIndex)) {
        processIndex <- 0
    }
    processIndex
}

#' Function to get process index from process ID
#' 
#' @inheritParams general_arguments
#' @param offset An integer value to offset the process by, where -1 means the previous process and 1 means the next process.
#' 
#' @export
#' 
getProcessIDByOffset <- function(projectPath, modelName, processID, offset = 0) {
    # Get the table linking process names and IDs:
    processIndexTable <- readProcessIndexTable(
        projectPath = projectPath, 
        modelName = modelName
    )
    processIndex <- which(processIndexTable$processID == processID)
    
    # Added 0 as output for processes that has not been run:
    if(!length(processIndex)) {
        processIndex <- 0
    }
    
    # Apply the offset:
    processIndex <- processIndex + offset
    
    processIndexTable[processIndex, "processID"]
}

#' Function to get process name from process ID
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
getProcessNameFromProcessID <- function(projectPath, modelName, processID) {
    # Get the table linking process names and IDs:
    processIndexTable <- readProcessIndexTable(
        projectPath = projectPath, 
        modelName = modelName
    )
    if(!NROW(processIndexTable)) {
        stop("Project ", projectPath, " is not open.")
    }
    
    # Extract the requested process names:
    thisProcessID <- processID
    processIndexTable[processID == thisProcessID, processName]
}


#' Get process ID from function name
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
getProcessIDFromFunctionName <- function(projectPath, modelName, functionName) {
    findProcess(
        projectPath = projectPath, 
        modelName = modelName, 
        functionName = functionName
    )$processID
}





getProcessOutputTextFilePath <- function(
    projectPath, 
    modelName, 
    processID, 
    file.ext, 
    processOutput = NULL
    )
{
    # Get the process name
    processName <- getProcessNameFromProcessID(projectPath, modelName, processID)
    
    
    # Get the folder to place the output files in (added subFolder named by the process on 2020-10-21):
    folderPath <- getProcessOutputFolder(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        type = "text", 
        subFolder = NULL
    )
    
    # Create the folder:
    if(!file.exists(folderPath)) {
        dir.create(folderPath, recursive = TRUE)
    }
    
    # Define the process output file path:
    if(file.ext %in% c("RData", "nc")) {
        # Define a single file output named by the process name:
        processID <- getProcessIDFromProcessName(projectPath = projectPath, modelName = modelName, processName = processName)$processID
        dataType <- getDataType(projectPath = projectPath, modelName = modelName, processID = processID)
        fileNameSansExt <- dataType
    }
    else {
        # Added on 2020-06-16. Add the data type in the file name only if multiple outputs, but not for RData files (default for analysis processes):
        # Changed onn 2021-03-10 to only use the names of the output, not prefixed by the process name:
        if(length(processOutput)) {
            #fileNameSansExt <- paste(processName, names(processOutput), sep = "_")
            fileNameSansExt <- names(processOutput)
        }
        else {
            fileNameSansExt <- processName
        }
    }
    
    # Add file extension:
    filePathSansExt <- file.path(folderPath, fileNameSansExt)
    filePath <- paste(filePathSansExt, file.ext, sep = ".")
    
    
    return(filePath)
}
    


    
# Function to write process output to a text file in the output folder:
writeProcessOutputTextFile <- function(processOutput, projectPath, modelName, processID, escape = TRUE) {
    
    # Return NULL for empty process output:
    if(length(processOutput)) {
        
        # Get the file extension:
        outputFileType <- getDefaultOutputFileType(processOutput)
        
        # Store the process output:
        if(outputFileType == "RData") {
            filePath <- getProcessOutputTextFilePath(
                projectPath = projectPath, 
                modelName = modelName, 
                processID = processID, 
                processOutput = processOutput, 
                file.ext = outputFileType
            )
            
            # Rename the process output to the datatype:
            dataType <- getDataType(projectPath = projectPath, modelName = modelName, processID = processID)
            assign(dataType, processOutput)
            
            # Write to RData file:
            save(list = dataType, file = filePath)
        }
        # Store the process output:
        else if(outputFileType == "nc") {
            filePath <- getProcessOutputTextFilePath(
                projectPath = projectPath, 
                modelName = modelName, 
                processID = processID, 
                processOutput = processOutput, 
                file.ext = outputFileType
            )
            
            file.copy(processOutput[[1]], filePath)
        }
        else {
            # Unlist introduces dots, and we replace by underscore:
            processOutput <- unlistToDataType(processOutput)
            
            filePath <- getProcessOutputTextFilePath(
                projectPath = projectPath, 
                modelName = modelName, 
                processID = processID, 
                processOutput = processOutput, 
                file.ext = outputFileType
            )
            
            mapply(
                reportFunctionOutputOne, 
                processOutput = processOutput, 
                filePath = filePath, 
                escape = escape
            )
        }
    }
    else {
        NULL
    }
}


#saveListElements <- function(x, file, ...) {
#    xNames <- names(x)
#    temp <- mapply(assign, xNames, x, MoreArgs= list(pos = parent.frame()))
#    save(list = xNames, file = file, envir = environment(), ...)
#} 


# Function for writing one element of the function output list:
reportFunctionOutputOne <- function(processOutputOne, filePath, escape = TRUE) {
    
    if("sf" %in% class(processOutputOne)) {
        
        # Write the file:
        #jsonObject <- geojsonio::geojson_json(processOutputOne)
        jsonObject <- jsonlite::prettify(geojsonsf::sf_geojson(processOutputOne, simplify = FALSE))
        
        # It seems this is no longer relevant as we moved from geojsonio to geojsonsf:
        # Hack to rermove all IDs from the geojson:
        #jsonObject <- removeIDsFromGeojson(jsonObject)
        
        # Changed on 2020-12-19 to simply using write, as it writes as actual geojson:
        #jsonlite::write_json(jsonObject, path = filePath)
        write(jsonObject, file = filePath)
    }
    # To be implemented
    #else if("StoX_shapefile" %in% class(processOutputOne)) {
    #    dirPath <- dirname(filePath)
    #    writeOGR(obj = processOutputOne, dsn = dirPath, driver = "ESRI Shapefile")
    #}
    else if("data.table" %in% class(processOutputOne)) {
        # Write the file:
        if(length(processOutputOne) == 0) {
            cat("", file = filePath)
        }
        else {
            # Changed on 2021-09-13 to quote strings, so as to avoid data.table::fread() from conerting numeric strings to numeric class:
            if(escape) {
                escapeTabAndNewline(processOutputOne)
            }
            data.table::fwrite(processOutputOne, filePath, sep = "\t", na = "NA", quote = TRUE, qmethod = "double")
        }
    }
    else if("matrix" %in% class(processOutputOne)) {
        # Write the file:
        if(length(processOutputOne) == 0) {
            cat("", file = filePath)
        }
        else {
            # Convert NAs to empty string to support keeping the string "NA" as NA in the written file:
            if(any(is.na(processOutputOne))) {
                processOutputOne[is.na(processOutputOne)] <- ""
            }
            data.table::fwrite(data.table::as.data.table(processOutputOne), filePath, col.names = FALSE, sep = ",", na = "", quote = FALSE)
            
            # Changed on 2021-01-12 to using na = "NA", and quote = FALSE, which stores NAs as NA (unquoted) and requires that NAs from the variable row length is stored as empty string in the process output (which will result in empty fields for those cells, identical to the previous na = ""):
            #data.table::fwrite(data.table::as.data.table(processOutputOne), filePath, col.names = FALSE, sep = ",", na = "NA", quote = FALSE)
        }
    }
    else if("ggplot" %in% class(processOutputOne)) {
        # Write the plot file:
        ggsaveApplyDefaults(processOutputOne, filePath)
    }
    else if(any(getRstoxFrameworkDefinitions("vectorClasses") %in% class(processOutputOne))) {
        # Write the file:
        if(length(processOutputOne) == 0) {
            cat("", file = filePath)
        }
        else {
            writeLines(as.character(processOutputOne), filePath)
        }
    }
    else {
        stop("Unknown process output: ", class(processOutputOne))
    }
}



ggsaveApplyDefaults <- function(x, filePath, overrideAttributes = list()) {
    
    # Replace any attributes given in the overrideAttributes:
    att <- attributes(x)
    attToOverride <- intersect(names(att), names(overrideAttributes))
    if(length(attToOverride)) {
        att[attToOverride] <- overrideAttributes[attToOverride]
    }
    
    arguments <- list(
        plot = x, 
        device = if("Format"      %in% names(att)) att$Format      
            else RstoxBase::getRstoxBaseDefinitions("defaultPlotOptions")$defaultPlotFileOptions$Format, 
        width  = if("Width"       %in% names(att)) att$Width
            else RstoxBase::getRstoxBaseDefinitions("defaultPlotOptions")$defaultPlotFileOptions$Width,
        height = if("Height"      %in% names(att)) att$Height
            else RstoxBase::getRstoxBaseDefinitions("defaultPlotOptions")$defaultPlotFileOptions$Height, 
        dpi    = if("DotsPerInch" %in% names(att)) att$DotsPerInch
            else RstoxBase::getRstoxBaseDefinitions("defaultPlotOptions")$defaultPlotFileOptions$DotsPerInch, 
        units = "cm"
    )
    
    # Set the file extension to the format:
    arguments$filename <- paste(tools::file_path_sans_ext(filePath), arguments$device, sep = ".")
    
    do.call(ggplot2::ggsave, arguments)
    
    return(arguments$filename)
}


removeIDsFromGeojson <- function(json) {
    json[[1]] <- gsub(",\\s*\\\"id\\\":[\\\"a-zA-Z1-9_]*", "", json[[1]])
    json
}



#' Function to flatten a list of output datat and add names from the levels of the list
#' 
#' @param processOutput A list of StoX output data.
#' @param sep The separator to use when constructing names for the final flat list, defaulted to underscore, but slash can also be useful e.g. for denoting groupes in a NetCDF4 file.
#' @param validOutputDataClasses A vector of valid output data classes, indicating when to stop the unlisting.
#' @param nlevel The number of levels to unlist through.
#' @param keepNonStandardAttributes Logical: If TRUE, keep attributes other than the standard \code{dim}, \code{names} and \code{dimnames}. 
#' 
#' @export
#' 
unlistToDataType <- function(processOutput, sep = "_", validOutputDataClasses = getRstoxFrameworkDefinitions("validOutputDataClasses"), nlevel = 2, keepNonStandardAttributes = FALSE) {
    
    # Save attributes:
    if(keepNonStandardAttributes) {
        nonStandardAttributes <- getNonStandardAttributes(processOutput)
    }
    
    # Unlist through nlevel levels:
    for(i in seq_len(nlevel)) {
        processOutput <- unlistOneStep(processOutput, sep = sep, validOutputDataClasses = validOutputDataClasses)
    }
    
    # Set attributes:
    if(keepNonStandardAttributes) {
        processOutput <- setNonStandardAttributes(processOutput, nonStandardAttributes)
    }
    
    
    return(processOutput)
}


unlistOneStep <- function(processOutput, sep = "_", validOutputDataClasses = getRstoxFrameworkDefinitions("validOutputDataClasses")) {
    
    # Unlist and add the names:
    if(!areAllValidOutputDataClasses(processOutput, validOutputDataClasses = validOutputDataClasses)){
        # A trick to prepare for unlist(). Add one list level to all elements which are valid class:
        validClass <- sapply(processOutput, isValidOutputDataClass)
        processOutput[validClass] <- lapply(processOutput[validClass], list)
        
        # Define the names of the files first, by pasting the level and the sub-level names separated by underscore:
        processOutputNames <- unlist(lapply(names(processOutput), function(x) if(length(names(processOutput[[x]]))) paste(x, names(processOutput[[x]]), sep = sep) else x))
        # Unlist down one level:
        processOutput <- unlist(processOutput, recursive = FALSE)
        # Add the names again:
        names(processOutput) <- processOutputNames
    }
    
    processOutput
}

# Function to check that all the output elements are of the valid classes:
areAllValidOutputDataClasses <- function(x, validOutputDataClasses = getRstoxFrameworkDefinitions("validOutputDataClasses")) {
    all(sapply(x, isValidOutputDataClass, validOutputDataClasses = validOutputDataClasses))
}

# Function to check that all the output elements are of the valid classes:
isValidOutputDataClass <- function(x, validOutputDataClasses = getRstoxFrameworkDefinitions("validOutputDataClasses")) {
    isValid <- getRelevantClass(x) %in% validOutputDataClasses
    return(isValid)
}

# Function to get the first class of the object x, except if the class "gg" is returned, in which case "ggplot" is returned instead if present as one of the classes of the object:
getRelevantClass <- function(x) {
    relevantClass <- RstoxData::firstClass(x)
    # ggplot objects have "gg" as first class and "gpglot" as second. RstoxFramework wishes to specify "ggplot" as valid class for clarity (and since "gg" seems to be a wider class). So using only first class as the most relevant class fails in this respect. Thus we add a specific check on the existence of "ggplot" in the classes of the object:
    if(relevantClass == "gg" && "ggplot" %in% class(x)) {
        relevantClass <- "ggplot"
    }
    return(relevantClass)
}


# Function to write process output to a memory file:
writeProcessOutputMemoryFiles <- function(processOutput, projectPath, modelName, processID, subFolder = NULL) {
    if(length(processOutput)) {
        # Get the path to the folder to place the memory file in:
        folderPath <- getProcessOutputFolder(projectPath = projectPath, modelName = modelName, processID = processID, type = "memory", subFolder = subFolder)
        
        # The the process output is a StoXNetCDF4File, then it is a temporary file that must be renamed to a memory file:
        if(length(processOutput) == 1 && "StoXNetCDF4File" %in% class(processOutput[[1]])) {
            
            # Define a StoXNetCDF4File object:
            StoXNetCDF4File <- createStoXNetCDF4FileDataType(file.path(folderPath, paste(names(processOutput), "nc", sep = ".")))
            
            # Create the folder if not existing:
            dir.create(folderPath, recursive = TRUE, showWarnings = FALSE)
            file.copy(processOutput[[1]], StoXNetCDF4File)
            
            # Update the processOutput:
            processOutput[[1]] <- StoXNetCDF4File
            
            return(processOutput)
        }
        # Write other files:
        else {
            writeProcessOutputElementsRDS(
                processOutput, 
                folderPath = folderPath, 
                writeOrderFile = TRUE,
                writeClassFile = TRUE
            )
            
            return(processOutput)
        }
    }
    else {
        NULL
    }
}


# Function to write process output to a memory file:
writeProcessOutputElementsRDS <- function(processOutput, folderPath, writeOrderFile = TRUE, writeClassFile = TRUE) {
    if(length(processOutput)) {
        # Create the folder if not existing:
        dir.create(folderPath, recursive = TRUE, showWarnings = FALSE)
        
        # If the processOutput is a list with one element of class "StoXNetCDF4File", it points to a NetCDF4 file to be copied to the memory folder
        ### if(length(processOutput) == 1 && "StoXNetCDF4File" %in% class(processOutput[[1]])) {
        ###     StoXNetCDF4File <- file.path(folderPath, names(processOutput))
        ###     file.copy(processOutput[[1]], StoXNetCDF4File)
        ###     
        ###     return(StoXNetCDF4File)
        ### }
        
        # Detect whether the output is a list of tables (depth 1) or a list of lists of tables (depth 2), and possibly a list of both tables and lists of tables (treated as depth 2):
        outputDepth <- getOutputDepth(processOutput)
        
        # Get the file paths of the memory files and prepare the processOutput for writing to these files:
        if(any(outputDepth == 1)) {
            writeProcessOutputRDS1(
                processOutput = processOutput[outputDepth == 1], 
                folderPath = folderPath, 
                writeOrderFile = writeOrderFile
            )
        }
        if(any(outputDepth == 2)) {
            writeProcessOutputRDS2(
                processOutput = processOutput[outputDepth == 2], 
                folderPath = folderPath, 
                writeOrderFile = writeOrderFile
            )
        }
        
        if(writeClassFile) {
            # Write the class file:
            if(writeOrderFile) {
                outputClass <- getOutputClass(processOutput, outputDepth)
                outputClassFileName <- file.path(folderPath, "outputClass.txt")
                write(outputClass, outputClassFileName)
            }
        }
        
        # Write the data types for bootstrap processes (which sets the dataType attribute to each process output, so we check the first). The Bootstrap functions sets the data types as attributes to the individual baseline process outputs, and then this is picked up by writeProcessOutputElementsRDS()):
        if("dataType" %in% names(attributes(processOutput[[1]]))) {
            dataType <- data.table::data.table(
                processName = names(processOutput), 
                dataType = sapply(processOutput, attr, "dataType")
            )
            dataTypeFileName <- file.path(folderPath, "dataType.txt")
            data.table::fwrite(dataType, dataTypeFileName)
        }
    }
    else {
        NULL
    }
}


# Get the class of the output elements:
getOutputClass <- function(processOutput, outputDepth) {
    if(any(outputDepth == 1)) {
        outputClass <- getRelevantClass(processOutput[[1]])
    }
    if(any(outputDepth == 2)) {
        outputClass <- getRelevantClass(processOutput[[1]][[1]])
    }
    
    return(outputClass)
}

# Get the class of the output elements:
readOutputClass <- function(projectPath, modelName, processID) {
    
    # Get the directory holding the output files:
    folderPath <- getProcessOutputFolder(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID, 
        type = "memory"
    )
    
    # Read the order file if present:
    outputClassFile <- file.path(folderPath, "outputClass.txt")
    if(file.exists(outputClassFile)) {
        outputClass <- readLines(outputClassFile)
    }
    else {
        warning("The file ",outputClassFile, " does not exist. Class guessed to be data.table")
        outputClass <- "data.table"
    }
    
    return(outputClass)
}

# Get the type of the output elements, one of "table", "geojson" and "filePath", for use in the GUI. The GUI acts accordingly, e.g. by showing the plot saved in a temporary file given by the filePath:
getOutputElementType <- function(outputClass) {
    getRstoxFrameworkDefinitions("outputTypes")[[outputClass]]
}



writeProcessOutputRDS1 <- function(processOutput, folderPath, writeOrderFile = TRUE) {
    #  Define the file paths for output depth 1:
    fileNamesSansExt <- names(processOutput)
    filePaths <- file.path(folderPath, fileNamesSansExt)
    
    # Wrap in a list to coordinate with using the saveRDSs():
    filePaths <- list(filePaths)
    processOutput <- list(processOutput)
    
        
    # Write the individual tables:
    mapply(writeMemoryFiles, processOutput, filePaths, writeOrderFile = writeOrderFile, ext = "rds")
}

writeProcessOutputRDS2 <- function(processOutput, folderPath, writeOrderFile = TRUE) {
    # Get the sub folder paths and create the folders:
    folderPaths <- file.path(folderPath, names(processOutput))
    lapply(folderPaths, dir.create, recursive = TRUE, showWarnings = FALSE)
    
    # Create the file names and add the folder paths to the file names (flattening the output):
    fileNamesSansExt <- vector("list", length(processOutput))
    # Add names of the elements which are lists of valid output data types (typically table):
    areValid <- sapply(processOutput, isValidOutputDataClass)
    fileNamesSansExt[areValid] <- as.list(names(processOutput)[areValid])
    
    # Add the names of the list for lists of valid output data types:
    fileNamesSansExt[!areValid] <- lapply(processOutput[!areValid], names)
    filePaths <- mapply(file.path, folderPaths, fileNamesSansExt, SIMPLIFY = FALSE)
    
    # Write the individual tables:
    mapply(writeMemoryFiles, processOutput, filePaths, writeOrderFile = writeOrderFile, ext = "rds")
}


# Function to get the depth of the data, 1 for a list of valid output data objects, and 2 for a list of such lists:
getOutputDepth <- function(outputData) {
    sapply(outputData, getOutputDepthOne)
}
getOutputDepthOne <- function(outputDataOne) {
    # If the outputDataOne has length 0 or is of valid output data classes, set outputDepth to 1:
    if(!length(outputDataOne) || isValidOutputDataClass(outputDataOne)) {
        outputDepth <- 1
    }
    # Else if outputDataOne is a list, check all elements:
    else if(length(outputDataOne) && is.list(outputDataOne)) {
        areValid <- sapply(outputDataOne, isValidOutputDataClass) | lengths(outputDataOne) == 0
        if(all(areValid)) {
            outputDepth <- 2
        }
        else {
            stop("StoX: Process output must be a list of objects defined by getRstoxFrameworkDefinitions(\"validOutputDataClasses\"), or a list of such lists (not a list of lists of such lists).")
        }
    }
    
    
    return(outputDepth)
}

# Function to get the folder of the memory files, 1 for all files in one folder, and 2 for a subfolders:
getFolderDepth <- function(folderPath) {
    # List the files in the folder:
    filePaths <- list.dirs(folderPath, recursive = FALSE)
    folderDepth <- 1
    if(length(filePaths)) {
        folderDepth <- 2
    }
    folderDepth
}



#' Delete the contents of the output folder of a model.
#' 
#' This function is run by \code{\link{runModel}} to clear off any files present in the output folder of a model. It should also be used by GUIs when running a model.
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
purgeOutput <- function(projectPath, modelName) {
    folderPath <- getProjectPaths(projectPath = projectPath, name = modelName)
    unlink(folderPath, recursive = TRUE, force = TRUE)
    dir.create(folderPath, recursive = TRUE)
}

#' Run processes of a model.
#' 
#' @inheritParams general_arguments
#' @inheritParams runProcess
#' @inheritParams Projects
#' @param force.restart Logical: If TRUE, start the processes even if the status file indicating that the model is being run exists. This is useuful when something crached in a preivous run, in which case the model is still appearing as running.
#' @param prugeStopFile Logical: Should the file that signals that the model should be stopped be deleted if present before running? This parameter does not yet seem to in use by any other function.
#' @param replaceDataList A list named by the processes to replace output data for. See \code{\link{runProcess}}.
#' @param replaceArgsList A list of \code{replaceArgs} holding parameters to replace in the function call, named by the processes to modify.
#' @param prependProcessList A list of \code{values} used in \code{\link{prependProcess}}, named by the processes to prepend a process to.
#' @param ... \code{replaceArgsList} can also be given directly.
#' 
#' @export
#' 
runProcesses <- function(
    projectPath, 
    modelName, 
    startProcess = 1, endProcess = Inf, 
    msg = TRUE, msg.GUI = FALSE, 
    save = TRUE, force.save = FALSE, saveProcessData = TRUE, Application = R.version.string, 
    force.restart = FALSE, 
    returnProcessOutput = FALSE, fileOutput = NULL, setUseProcessDataToTRUE = TRUE, 
    purge.processData = FALSE, 
    replaceDataList = list(), 
    replaceArgsList = list(), 
    prependProcessList = list(), 
    try = TRUE, 
    prugeStopFile = FALSE, 
    ...
) {
    
    #  If force.save, set save to TRUE:
    if(force.save)  {
        save  = TRUE
    }
    
    # Open the project if not open:
    if(!isOpenProject(projectPath)) {
        # No need for Application here as runProcesses() should be used after opening the project in a Application:
        openProject(projectPath, ...)
    }
    
    if(length(prependProcessList)) {
        mapply(
            prependProcess, 
            processName = names(prependProcessList), 
            values = prependProcessList, 
            MoreArgs = list(
                projectPath = projectPath, 
                modelName = modelName
            )
        )
    }
    
    # Save both before and after for safety:
    if(save) {
        saveProject(
            projectPath, 
            msg = FALSE, 
            Application = Application, 
            force = force.save
        )
    }
    
    # Get the processIDs:
    processIndexTable <- readProcessIndexTable(projectPath, modelName, startProcess = startProcess, endProcess = endProcess)
    processIDs <- processIndexTable$processID
    processNames <- processIndexTable$processName
    if(!length(processIDs)) {
        #warning("StoX: Empty model ", modelName, " of project, ", projectPath)
        return(NULL)
    }
    
    # Check for parameters to override the processes by in "...":
    #replaceArgs <- getReplaceArgs(replaceArgs, ..., processNames = processNames)
    replaceArgsList <- getreplaceArgsList(replaceArgsList, ...)
    
    # Check that the project exists:
    failedVector <- logical(length(processIDs))
    if(!isProject(projectPath)) {
        warning("StoX: The StoX project ", projectPath, " does not exist")
        return(failedVector)
    }
    # Check that the model exists
    else if(!modelName %in% getRstoxFrameworkDefinitions("stoxModelNames")){
        warning("StoX: The modelName must be one of ", paste(getRstoxFrameworkDefinitions("stoxModelNames"), collapse = ", "), " (was ", modelName, ")")
        return(failedVector)
    }
    
    
    # Check that the project is open:
    if(!isOpenProject(projectPath)) {
        warning("StoX: The StoX project ", projectPath, " is not open. Use RstoxFramework::openProject() to open the project.")
        return(failedVector)
    }
    
    # Chech that none of the models of the project are running:
    if(isRunning(projectPath, modelName) && !force.restart) {
        warning("StoX: The project is running (", projectPath, "). Close and open the project (or use force.restart = TRUE in runModel() in R).")
        return(failedVector)
    }
    else {
        setRunning(projectPath, modelName)
    }
    
    on.exit({
        setNotRunning(projectPath, modelName)
    })
    
    # Loop through the processes:
    if(prugeStopFile) {
        stopFile <- getProjectPaths(projectPath, "stopFile")[[modelName]]
        if(file.exists(stopFile)) {
            unlink(stopFile, force = TRUE, recursive = TRUE)
        }
    }
    
    
    replaceArgsListFull <- structure(vector("list", length(processIDs)), names = processNames)
    valid <- intersect(names(replaceArgsListFull), names(replaceArgsList))
    replaceArgsListFull[valid] <- replaceArgsList[valid]
    mapply(
        runProcess, 
        processID = processIDs, 
        replaceArgs = replaceArgsListFull, 
        replaceData = replaceDataList[processNames], 
        MoreArgs = list(
            projectPath = projectPath, 
            modelName = modelName, 
            msg = msg, msg.GUI = msg.GUI, 
            saveProcessData = saveProcessData, returnProcessOutput = returnProcessOutput,
            fileOutput = fileOutput, 
            setUseProcessDataToTRUE = setUseProcessDataToTRUE, 
            purge.processData = purge.processData, 
            #replaceArgs = replaceArgsList, 
            try = try
        )
    )
    
    
   #     }#, 
        #error = function(e) {
        #    err <<- e
        #    stop(err)
        #}, 
        #finally = {
        #    
            
            #if(length(err)) {
            #    stop(err)
            #}
            
            #return(status)
        #}
    #)
    
    #status
    
    # Save the project after each run:
    if(save) {
        saveProject(
            projectPath, 
            msg = FALSE, 
            Application = Application, 
            force = force.save
        )
    }
    
    #status
    list(
        processTable = getProcessTable(projectPath = projectPath, modelName = modelName), 
        interactiveMode = getInteractiveMode(projectPath = projectPath, modelName = modelName, processID = utils::tail(processIDs, 1)), 
        activeProcess = getActiveProcess(projectPath = projectPath, modelName = modelName), 
        saved = isSaved(projectPath)
    )
}


# Function to merge the replaceArgsList list and the ... input:
getreplaceArgsList <- function(replaceArgsList = list(), ...){
    
    # Get the specifications given as '...':
    dotlist <- list(...)
    
    # Merge and unique the inputs:
    replaceArgsList <- c(replaceArgsList, dotlist)
    # parlist <- unique(parlist) THIS REMOVED THE NAMES AND SHOULD NOT BE USED
    replaceArgsList <- replaceArgsList[!(duplicated(replaceArgsList) & duplicated(names(replaceArgsList)))]
    
    return(replaceArgsList)
}




# Check that a process has been run:
hasBeenRun <- function(projectPath, modelName, processID) {
    processIndex <- getProcessIndexFromProcessID(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = processID
    )
    activeProcessIndex <- getProcessIndexFromProcessID(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = getActiveProcess(
            projectPath = projectPath, 
            modelName = modelName
        )$processID
    )
    # TRUE if the process is not later than the active process:
    if(processIndex < activeProcessIndex) {
        out <- TRUE
    }
    # If the active process, whech whether it is ditry:
    else if(processIndex == activeProcessIndex) {
        out <- getActiveProcess(
            projectPath = projectPath, 
            modelName = modelName
        )$processDirty
    }
    else {
        out <- FALSE
    }
    
    return(out)
}








#' Find a processes from processName or functionName
#' 
#' @inheritParams general_arguments
#' 
#' @export
#' 
findProcess <- function(projectPath, modelName, processName = NULL, functionName = NULL) {
    
    # Get the table of baseline processes:
    processTable <- getProcessAndFunctionNames(
        projectPath = projectPath, 
        modelName = modelName
    )
    
    if(length(processName)) {
        atProcess <- matchProcesses(processName, processTable)
        processTable <- processTable[atProcess, ]
    }
    else if(length(functionName)) {
        # Find the processes that use the given function:
        functionNames <- getFunctionNameFromPackageFunctionName(processTable$functionName)
        atFunctionName <- which(functionNames == functionName)
        processTable <- processTable[atFunctionName,]
    }
    
    return(processTable)
}


#' Prepend (append before) to a process by adding another process of the same function
#' 
#' This method is only available for functions that has the same data type as input and output.
#' 
#' @inheritParams general_arguments
#' @param prependProcessName The name of process to prepend.
#' @param values A list of zero or more of the following process arguments: functionParameters, processData, processParameters. The arguments functionInputs, functionName and processName are set as equal to the process given by \code{processName}.
#' 
#' @export
#' 
prependProcess <- function(projectPath, modelName, processName, prependProcessName = paste(processName, "prepended", sep = "_"), values = NULL, returnProcessTable = TRUE) {
    
    # Get the process:
    processTable <- getProcessesSansProcessData(
        projectPath = projectPath, 
        modelName = modelName
    )
    atProcess <- matchProcesses(processName, processTable, warn = FALSE)
    if(!length(atProcess)) {
        return(FALSE)
    }
    process <- processTable[atProcess, ]
    
    # This function can only be run on processes that input and output the same data type:
    if(!readsItsOwnOutputDataType(process$functionName)) {
        stop("Only processes that input and output the same data type can be prepended.")
    }
    
    # Get the output data type of the process:
    outputDataType <- getFunctionOutputDataType(process$functionName)
    
    # Create the new process, and add the same input as in the existing process (only for the single output data type of the function, so this can be used mostly for filter functions):
    values$functionInputs[[outputDataType]] <- process$functionInputs[[1]][[outputDataType]]
    
    # Add function name
    values$functionName <- process$functionName
    # Add process name
    values$processName <- prependProcessName
    
    addProcess(projectPath, modelName, values = values, beforeProcessID = process$processID)
    
    # Change the input to the existing process:
    modifyFunctionInputs(
        projectPath = projectPath, 
        modelName = modelName, 
        processID = process$processID, 
        newFunctionInputs = structure(list(prependProcessName), names = outputDataType), 
        archive = TRUE
    )
    
    
    if(returnProcessTable) {
        processTable <- getProcessTable(projectPath = projectPath, modelName = modelName)
        activeProcess <- getActiveProcess(projectPath = projectPath, modelName = modelName)
        return(
            list(
                processTable = processTable, 
                activeProcess = activeProcess, 
                saved = isSaved(projectPath)
            )
        )
    }
    else {
        return(TRUE)
    }
}
StoXProject/RstoxFramework documentation built on Oct. 17, 2023, 1:24 p.m.