# TODO: tidyselect:::where has only been properly exported in August 2022, see here: https://github.com/r-lib/tidyselect/pull/274
# As a workaround, as recommended, we use this here for now:
utils::globalVariables("where")

############### PCA ###############

# TODO: Currently, the "standard PCA workflow with vst transform based on raw counts is not possible anymore, the normalized counts are taken as they are

#' Produce a PCA plot of the data from a \code{\linkS4class{GRN}} object
#'
#' @template GRN 
#' @template outputFolder
#' @param data Character. Either \code{"peaks"} or \code{"rna"} or \code{"all"}. Default \code{c("rna", "peaks")}. Type of data to plot a PCA for. \code{"peaks"} corresponds to the the open chromatin data, while \code{"rna"} refers to the RNA-seq counts. If set to \code{"all"}, PCA will be done for both data modalities. In any case, PCA will be based on the original provided data before any additional normalization has been run (i.e., usually the raw data).
#' @param topn Integer vector. Default \code{c(500,1000,5000)}. Number of top variable features to do PCA for. Can be a vector of different numbers (see default).
#' @param type Character. Must be \code{"normalized"}. On which data type (raw or normalized) should the PCA plots be done? We removed support for \code{raw} and currently only support \code{normalized}.
#' @param removeFiltered Logical. \code{TRUE} or \code{FALSE}. Default \code{TRUE}. Should features marked as filtered as determined by \code{filterData} be removed?
#' @template forceRerun
#' @template basenameOutput
#' @template plotAsPDF
#' @template pdf_width
#' @template pdf_height
#' @template pages
#' @return An updated \code{\linkS4class{GRN}} object with the data of the screeplot and PCA stored in GRN@stats$PCA. Already existing slots are overwritten.
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plotPCA_all(GRN, topn = 500, data = "rna", type = "normalized", plotAsPDF = FALSE, pages = 1)
#' @export
plotPCA_all <- function(GRN, outputFolder = NULL, basenameOutput = NULL, 
                        data = c("rna", "peaks"), topn = c(500,1000,5000), type = "normalized", removeFiltered = TRUE,
                        plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12, pages = NULL,
                        forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    GRN = .addFunctionLogToObject(GRN)
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assertSubset(data , c("rna", "peaks", "all"), empty.ok = FALSE)
    checkmate::assertChoice(type , c("normalized"))
    #checkmate::assertSubset(type , c("raw", "normalized", "all"), empty.ok = FALSE)
    checkmate::assertFlag(removeFiltered)
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    if (is.null(GRN@stats$PCA)) {
        GRN@stats$PCA = list()
    }
    
    if (is.null(GRN@data$metadata)) {
        message = "Slot GRN@data$metadata is NULL"
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    }
    
    
    # Initialize the page counter
    pageCounter = 1 
    
    # For normalized data, we do not do any other transformation currently and plot the normalized data "as is"
    # Changed on 08.02.22: We now never do a(nother) log transform, as the input for the density plot is already transformed counts. 
    transformationCur = "none"
    logTransformDensity = FALSE
    
    if ("rna" %in% data | "all" %in% data) {
        
        matrixCur = getCounts(GRN, type = "rna", permuted = FALSE, asMatrix = TRUE, includeFiltered = !removeFiltered)
        
        fileCur = paste0(outputFolder, 
                         ifelse(is.null(basenameOutput), .getOutputFileName("plot_pca"), basenameOutput),
                         "_RNA.", type, ".pdf")
        if (!file.exists(fileCur) | forceRerun) {
            
            futile.logger::flog.info(paste0("Plotting PCA and metadata correlation of ", type, 
                                            " RNA data for all shared samples. This may take a few minutes"))
            
            GRN = .plot_PCA_wrapper(GRN, slot = "rna", counts = matrixCur, topn = topn, 
                                    transformation = transformationCur, 
                                    logTransformDensity = logTransformDensity, 
                                    file = fileCur, pdf_width = pdf_width, pdf_height = pdf_height, pages = pages, plotAsPDF = plotAsPDF)
            
        } else {
            futile.logger::flog.info(paste0("File ", fileCur, " already exists, not overwriting due to forceRerun = FALSE"))
            
        }
        
        
    } # end if plot RNA PCA
    
    if ("peaks" %in% data | "all" %in% data) {
        
        matrixCur = getCounts(GRN, type = "peaks", permuted = FALSE, asMatrix = TRUE, includeFiltered = !removeFiltered)
        
        fileCur = paste0(outputFolder, ifelse(is.null(basenameOutput), 
                                              .getOutputFileName("plot_pca"), basenameOutput), "_peaks.", type, ".pdf")
        
        if (!file.exists(fileCur) | forceRerun) {
            
            futile.logger::flog.info(paste0("Plotting PCA and metadata correlation of ", type, 
                                            " peaks data for all shared samples. This may take a few minutes"))
            
            GRN = .plot_PCA_wrapper(GRN, slot = "peaks", matrixCur, transformation = transformationCur, logTransformDensity = logTransformDensity, file = fileCur, topn = topn, pdf_width = pdf_width, pdf_height = pdf_height, pages = pages, plotAsPDF = plotAsPDF)
            
        } else {
            futile.logger::flog.info(paste0("File ", fileCur, " already exists, not overwriting due to forceRerun = FALSE"))
            
        }
        
    }
    
    .printExecutionTime(start, prefix = "") 
    GRN
    
    
}


.plot_PCA_wrapper <- function(GRN, slot, counts, transformation = "vst", scale = TRUE, logTransformDensity, file, topn, pdf_width, pdf_height, pages = NULL, plotAsPDF) {
    
    # Reset PCA results slot
    if (is.null(GRN@stats$PCA[[slot]])) {
        GRN@stats$PCA[[slot]] = list()
    }
    
    
    checkmate::assertChoice(transformation, c("vst", "log2", "none"))
    checkmate::assertIntegerish(topn, lower = 50, any.missing = FALSE,min.len = 1)
    
    if (transformation == "vst") {
        checkmate::assertClass(counts, "DESeqDataSet")
        # Use ALL genes for variance stabilization, and not only the top X
        dd.vst = DESeq2::varianceStabilizingTransformation(counts)
        counts.transf = SummarizedExperiment::assay(dd.vst)
        metadata = SummarizedExperiment::colData(dd.vst) %>% as.data.frame()
        
        
        
    } else if (transformation == "log2") {
        checkmate::assertMatrix(counts)
        counts.transf = log2(counts + 1)
        metadata = GRN@data$metadata %>% dplyr::filter(.data$sampleID %in% colnames(counts.transf)) %>% tibble::column_to_rownames("sampleID") %>% as.data.frame()
        
    } else {
        checkmate::assertMatrix(counts)
        counts.transf = counts
        metadata = GRN@data$metadata %>% dplyr::filter(.data$sampleID %in% colnames(counts.transf)) %>% tibble::column_to_rownames("sampleID") %>% as.data.frame()
    }
    
    # Override scaling when already vst-transformed
    # See https://support.bioconductor.org/p/60531/
    if (transformation != "none") {
        scale = FALSE
        futile.logger::flog.info(paste0("Set scale = FALSE as data has already been transformed"))
    }
    
    # Add sampleID as explicit column so it is alwys included in the PCA plot to identify outliers
    metadata$sampleID = rownames(metadata)
    
    futile.logger::flog.info(paste0("Prepare PCA. Count transformation: ", transformation))
    
    pageCounter = 1 
    if (plotAsPDF) {
        .checkOutputFile(file)
        grDevices::pdf(file, width = pdf_width, height = pdf_height)
        futile.logger::flog.info(paste0("Plotting to file ", file))
    } else {
        futile.logger::flog.info(paste0("Plotting directly"))
    }
    
    # calculate the variance for each gene
    rv <- matrixStats::rowVars(counts.transf)
    
    # Remove rows with 0 variance, these cause errors later on
    zeroVar = which(rv == 0)
    if (length(zeroVar) > 0) {
        message = paste0("plotPCA: Removing ", length(zeroVar), " rows (out of ", nrow(rv), " in total) from count data because they have 0 variance")
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
        rv = rv[-zeroVar]
        counts.transf = counts.transf[-zeroVar,]
    }
    
    
    # 0. Density plot for ALL features
    if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
        .plotDensity(counts.transf, logTransform = logTransformDensity, legend = dplyr::if_else(ncol(counts.transf) > 20, FALSE, TRUE))
        
    }
    pageCounter = pageCounter + 1
    
    for (topnCur in topn) {
        
        listnameCur = paste0("topn", topnCur)
        
        if (is.null(GRN@stats$PCA[[slot]][[listnameCur]])) {
            GRN@stats$PCA[[slot]][[listnameCur]] = list()
        }
        
        # select the ntop genes by variance
        select <- order(rv, decreasing = TRUE)[seq_len(min(topnCur, length(rv)))]
        
        # perform a PCA on the data in assay(x) for the selected genes
        # For already normalized data, scale is not necessary
        pca <- stats::prcomp(t(counts.transf[select,]), scale = scale)
        
        nPCS = min(10, ncol(pca))
        
        # the contribution to the total variance for each component
        percentVar <- pca$sdev^2 / sum( pca$sdev^2 )
        
        screeplot.df = tibble::tibble(PCs = factor(paste0("PC ", seq_len(nPCS)), levels = paste0("PC ", seq_len(nPCS))), variation = percentVar[seq_len(nPCS)] * 100) %>%
            dplyr::mutate(variation_sum = cumsum(.data$variation),
                          pos = seq_len(nPCS))
        
        GRN@stats$PCA[[slot]][[listnameCur]][["data_screeplot"]] = screeplot.df
        
        # 1. Scree plot
        if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
            
            g = ggplot2::ggplot(screeplot.df, ggplot2::aes(.data$PCs, .data$variation)) + ggplot2::geom_bar(stat = "identity") + 
                ggplot2::xlab("Principal component (PC)") + ggplot2::ylab("Explained variation / Cumultative sum (in %)") + 
                ggplot2::geom_point(data = screeplot.df, ggplot2::aes(.data$pos, .data$variation_sum), color = "red") + 
                ggplot2::geom_line(data = screeplot.df, ggplot2::aes(.data$pos, .data$variation_sum), color = "red") +
                ggplot2::ggtitle(paste0("Screeplot for top ", topnCur, " variable features")) + 
                ggplot2::theme_bw()
            
            plot(g)
        }
        pageCounter = pageCounter + 1
        
        # 2. Metadata correlation with PCs
        if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
            metadata_columns = .findSuitableColumnsToPlot(metadata, remove1LevelFactors = TRUE, verbose = FALSE)
            .plotPCA_QC(pcaResult = pca, dataCols = colnames(counts.transf), metadataTable = metadata, metadataColumns = metadata_columns, 
                        varn = topnCur, metadataColumns_fill = metadata_columns, 
                        file = NULL, logTransform = FALSE)
        }
        pageCounter = pageCounter + 1
        
        
        # 3. Correlation plots colored by metadata
        if (is.null(GRN@stats$PCA[[slot]][[listnameCur]][["data_PCA"]])) {
            GRN@stats$PCA[[slot]][[listnameCur]][["data_PCA"]] = list()
        }
        metadata_columns = .findSuitableColumnsToPlot(metadata, remove1LevelFactors = FALSE, verbose = FALSE)
        for (varCur in metadata_columns) {
            
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                
                plotTitle = paste0("Top ", topnCur, " variable features, colored by \n", varCur)
                
                skipLegend = FALSE
                vecCur = metadata[, varCur] %>% unlist()
                if (is.character(vecCur) & dplyr::n_distinct(vecCur) > 30) {
                    skipLegend = TRUE
                    plotTitle = paste0(plotTitle, " (legend skipped)")
                }
                
                result = tryCatch({
                    
                    # The following code is taken from plotPCA from DESeq2 and adapted here to be more flexible
                    intgroup.df <- as.data.frame(metadata[, varCur, drop = FALSE])
                    
                    # add the varCur factors together to create a new grouping factor
                    group = metadata[[varCur]]
                    
                    # assembly the data for the plot
                    d <- data.frame(group = group, intgroup.df, name = colnames(counts.transf))
                    
                    for (pcCur in 1:min(10, ncol(pca$x))) {
                        d[colnames(pca$x)[pcCur]] = pca$x[,pcCur]
                    }
                    
                    d = d %>%
                        dplyr::mutate_if(is.character, as.factor) %>%
                        dplyr::mutate_if(is.logical, as.factor)
                    
                    # Save the PCA data somewhere
                    GRN@stats$PCA[[slot]][[listnameCur]][["data_PCA"]] [[varCur]] = d
                    
                    g = g = ggplot2::ggplot(data = d, ggplot2::aes(x = .data$PC1, y = .data$PC2, color = group)) + 
                        ggplot2::geom_point(size = 3) + 
                        ggplot2::xlab(paste0("PC1: ",round(percentVar[1] * 100, 1),"% variance")) +
                        ggplot2::ylab(paste0("PC2: ",round(percentVar[2] * 100, 1),"% variance")) +
                        ggplot2::ggtitle(plotTitle) + ggplot2::coord_fixed()
                    
                    
                    
                    if (is.factor(d$group)) {
                        g = g + ggplot2::scale_color_viridis_d()
                        
                    } else {
                        
                        is.date <- function(x) inherits(x, 'Date')
                        
                        if (!is.date(d$group)) {
                            g = g + ggplot2::scale_color_viridis_c()
                        } else {
                            g = g + ggplot2::scale_color_viridis_c(trans = "date")
                        }
                        
                        
                    }
                    
                    
                    if (skipLegend) {
                        g = g + ggplot2::theme(legend.position = "none")
                    } 
                    
                    plot(g)
                    
                }, error = function(e) {
                    message = paste0("plotPCA: Could not plot PCA with variable ", varCur)
                    .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)   
                    
                    plot(c(0, 1), c(0, 1), ann = FALSE, bty = 'n', type = 'n', xaxt = 'n', yaxt = 'n')
                    message = paste0(" Could not plot PCA with variable ", varCur)
                    text(x = 0.5, y = 0.5, message, cex = 1.6, col = "red")
                })
            }
            pageCounter = pageCounter + 1
            
            
        } # end for each variable
        
    }
    
    .checkPageNumberValidity(pages, pageCounter)
    if (plotAsPDF) grDevices::dev.off()
    
    GRN
    
}


.plotDensity <- function(data, logTransform = TRUE, legend = FALSE) {
    
    if (logTransform) {
        data = log2(data + 1)
    } 
    
    data.df = reshape2::melt(tibble::as_tibble(data), measure.vars = colnames(data)) %>% dplyr::rename(sample = "variable")
    g = ggplot2::ggplot(data.df, ggplot2::aes(.data$value, color = .data$sample)) + ggplot2::geom_density() + ggplot2::theme_bw()
    
    title = paste0("Density of values per sample for all features")
    if (!legend) {
        g = g + ggplot2::theme(legend.position = "none")
        title = paste0(title, " (Legend omitted, too many samples)")
    }
    g = g + ggplot2::ggtitle(title)
    
    plot(g)
    
}

# data: Matrix of your data, with column names. May or may not be vsd transformed, log2 transformed etc
# metadata: The metadata data frame. Requires row.names that are identical to the colnames in data. See examples below
# varn: Number of most variable rows (features) to take.
# metadataColumns: Covariates you want to compare against PCs in character form. Must be a subset of the column names of metadata
# scale: prcomp is used for PCA, should your data be scaled? Default TRUE, scaling is advisable
# pcCount: How many PCs do you want to compare against?
# metadataColumns_fill: One of the metadataColumns variables or multiple ones
# logTransform: log2 the data for the density plot?
# ... Used for the pdf function, e.g., for specifying the pdf page sizes

# Typical use case:
# library(SummarizedExperiment)
# plotPCA_QC(assay(vsd), metadata = coldataCur, metadataColumns = PCAVariables_red, varn  = 500, metadataColumns_fill = PCAVariables_red, scale = TRUE, pcCount = 5, logTransform = FALSE, file = "~/PCA.pdf", height = 10, width = 10)

# EXAMPLE
# data = matrix(rnorm(1000 * 10), nrow = 1000, dimnames = list(c(1:1000), paste0("sample", 1:10)))
# p = rep(0.5,10)
# metadata = data.frame(sampleID = paste0("sample", 1:10), 
#                       gender   = rep(c("male","female"), 5), 
#                       age      = sample.int(100, 10),
#                       obese    = runif (length(p)) < p,
#                      row.names = paste0("sample", 1:10) )

# metadataColumns = colnames(metadata)
# metadataColumns_fill = c("gender", "age", "obese") # Could be a subset of all metadata
# varn = 500  # Number of most variable rows in the data for calculating the associations, 
# pcCount = 5 # Number of PCs to correlate metadata with

# plotPCA_QC(data = data, metadata = metadata, metadataColumns = metadataColumns, varn = varn, metadataColumns_fill = metadataColumns_fill, 
#            scale = TRUE, pcCount = pcCount, file = "test.pdf")

.plotPCA_QC <- function(pcaResult, dataCols, metadataTable, metadataColumns, varn = 500, metadataColumns_fill, pcCount = 10, file = NULL, logTransform = FALSE,  pdf_width = 7, pdf_height = 7) {
    
    
    checkmate::assertDataFrame(metadataTable)
    metadataTable = as.data.frame(metadataTable)
    checkmate::assertIntegerish(varn, lower = 100)
    checkmate::assertFlag(logTransform)
    
    checkmate::assertSubset(metadataColumns, colnames(metadataTable), empty.ok = FALSE)
    checkmate::assertSubset(metadataColumns_fill, metadataColumns, empty.ok = FALSE)
    
    # Enforce checking that they are identical
    checkmate::assertSetEqual(rownames(metadataTable), dataCols)
    
    futile.logger::flog.info(paste0("  Performing and summarizing PCs across metadata for top ", varn, " features", ifelse(is.null(file), "", paste0(" to file ", file))))
    
    # Helper functions
    r2_fun <- function(x, y) {
        summary(stats::lm(y ~ x))$r.squared
    } #function to get adj. R2 values
    
    
    if (!is.null(file)) {
        .checkOutputFile(file)
        grDevices::pdf(file, width = pdf_width, height = pdf_height)
    }
    
    pc = pcaResult
    
    # The number of PCs may be smaller than the asked number, depending on the tol parameter. Check here
    if (pcCount > ncol(pc$x)) {
        message = paste0("plotPCA: Only ", ncol(pc$x), " PCs can be used instead of ",pcCount )
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
        pcCount = ncol(pc$x)
    }
    
    pcs_cv <- pc$x[,seq_len(pcCount)]   #Top PCs from top 1000 variable genes
    
    eig.val <- 100*summary(pc)$importance[2,seq_len(pcCount)]
    
    metadataTable = dplyr::mutate_if(metadataTable, is.character, as.factor)
    
    covariates <- data.frame(metadataTable[,metadataColumns], row.names = dataCols)
    
    r2_cv <- matrix(NA, nrow = ncol(covariates), ncol = ncol(pcs_cv), dimnames = list(colnames(covariates), colnames(pcs_cv)))
    
    for (cov in colnames(covariates)) {
        for (pc in colnames(pcs_cv)) {
            r2_cv[cov, pc] <- r2_fun(covariates[,cov], pcs_cv[,pc])
        }
    }  #get adj. R2 value for covariates ~ PC 1-6
    
    colnames(r2_cv) = paste0(colnames(pcs_cv), " (", round(eig.val, 1),  " %)")
    
    breaks = seq(0,1,0.02)
    colors = grDevices::colorRampPalette(c("white", "red"))(length(breaks))
    #colors = colorRampPalette((brewer.pal(n = 7, name = "Reds")))(length(breaks))
    
    
    ComplexHeatmap::Heatmap(
        r2_cv,
        name = "Correlation",
        col = colors,
        cluster_columns = FALSE, cluster_rows = TRUE,
        row_names_side = "right", row_names_gp = grid::gpar(fontsize = 10), 
        column_title = paste0("Metadata correlation with PCs for top ", varn, " features"),
        column_names_gp = grid::gpar(fontsize = 10),
        heatmap_legend_param = list(title = "Correlation\nwith PC",  legend_direction = "vertical"),
        row_title = "Metadata",
        cell_fun = function(j, i, x, y, width, height, fill) {
            grid::grid.text(sprintf("%.2f", r2_cv[i, j]), x, y, gp = grid::gpar(fontsize = 10))
        }
    ) %>% plot()
    
    
    
    if (!is.null(file)) {
        grDevices::dev.off()
    }
}



############### TF-peak QC ###############




#' Plot diagnostic plots for TF-peak connections for a \code{\linkS4class{GRN}} object
#' 
#' @template GRN 
#' @template outputFolder
#' @template basenameOutput
#' @template plotDetails
#' @param dataType Character vector. One of, or both of, \code{"real"} or \code{"background"}. For which type, real or background data, to produce the diagnostic plots?
#' @param nTFMax \code{NULL} or Integer > 0. Default \code{NULL}. Maximum number of TFs to process. Can be used for testing purposes by setting this to a small number i(.e., 10)
#' @template plotAsPDF
#' @template pdf_width
#' @param pdf_height_base  Number. Default 8. Base height of the PDF, in cm, per connection type. The total height is automatically determined based on the number of connection types that are found in the object (e.g., expression or TF activity). For example, when two connection types are found, the base height is multiplied by 2.
#' @template pages
#' @template forceRerun
#' @return An updated \code{\linkS4class{GRN}} object.
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plotDiagnosticPlots_TFPeaks(GRN, outputFolder = ".", dataType = "real", nTFMax = 2, pages = 1)
#' @export
plotDiagnosticPlots_TFPeaks <- function(GRN, 
                                        outputFolder = NULL, 
                                        basenameOutput = NULL, 
                                        plotDetails = FALSE,
                                        dataType = c("real", "background"),
                                        nTFMax = NULL,
                                        plotAsPDF = TRUE, pdf_width = 12, pdf_height_base = 8, pages = NULL,
                                        forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    checkmate::assertFlag(plotDetails)
    checkmate::assertSubset(dataType, c("real", "background"), empty.ok = FALSE)
    checkmate::assert(checkmate::checkNull(nTFMax), checkmate::checkIntegerish(nTFMax, lower = 1))
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height_base, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    
    useGCCorrection = GRN@config$parameters$useGCCorrection
    checkmate::assertFlag(useGCCorrection, na.ok = FALSE)
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    dataType2 = c()
    if ("real" %in% dataType) dataType2 = c(0)
    if ("background" %in% dataType) dataType2 = c(dataType2, 1)
    
    for (permutationCur in 0:.getMaxPermutation(GRN)) {
        
        if (!permutationCur %in% dataType2) {
            next
        }
        
        futile.logger::flog.info(paste0("\n Plotting for ", .getPermStr(permutationCur)))
        
        
        suffixFile = .getPermutationSuffixStr(permutationCur)
        
        fileCur = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_TFPeak_fdr"), basenameOutput), 
                         suffixFile, ".pdf")
        
        
        if (!file.exists(fileCur) | !plotAsPDF | forceRerun) {
            
            GRN = .addFunctionLogToObject(GRN)
            
            if (!plotAsPDF) fileCur = NULL
            
            # Total number of pages is nTFMax plus 1 page per connection type
            nPagesMax = nTFMax + length(GRN@config$TF_peak_connectionTypes)
            
            heightCur = pdf_height_base * length(GRN@config$TF_peak_connectionTypes)
            .plot_TF_peak_fdr(GRN, perm = permutationCur, useGCCorrection = useGCCorrection, 
                              plotDetails = plotDetails, fileCur, width = pdf_width, height = heightCur,
                              nPagesMax = nPagesMax, pages = pages) 
        }
        
    }
    
    .printExecutionTime(start, prefix = "")
    GRN
    
    
}



.plot_TF_peak_fdr <- function(GRN, perm, useGCCorrection, 
                              plotDetails = FALSE, file = NULL, width = 7, height = 7, 
                              nPagesMax = NULL, pages = NULL) {
    
    start = Sys.time()
    
    pageCounter = 1  
    
    if (!is.null(file)) {
        .checkOutputFile(file)
        grDevices::pdf(file, width = width, height = height)
    } 
    
    futile.logger::flog.info(paste0("Plotting FDR summary and curves for each TF", ifelse(is.null(file), "", paste0(" to file ", file))))
    
    
    # TODO: handle multiple activities and actually write this function
    # fileCur = paste0(outputFolder, .getOutputFileName("plot_TFPeak_TFActivity_QC"), suffixFile)
    # if ("TFActivity" %in% GRN@config$TF_peak_connectionTypes & (!file.exists(fileCur) | forceRerun)) {
    #   .plotTF_peak_TFActivity_QC(GRN, perm = permutationCur, fileCur, width = 7, height = 8) 
    # }
    
    
    # Plot the connection summary first
    fdrToInclude = c(0.001, 0.01, 0.05, 0.1, 0.2, 0.3)
    
    for (TF_peak_connectionType_cur in GRN@config$TF_peak_connectionTypes) {
        if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
            .plot_TF_peak_connectionSummary(GRN, fdr = fdrToInclude, TF_peak_connectionType = TF_peak_connectionType_cur)
        }
        pageCounter = pageCounter + 1
        
    }
    
    #We can skip altogether if all pages have already been produced
    if (is.null(pages) | (!is.null(pages) && any(pages >= pageCounter))) {
        
        # Dont take all TF, some might be missing.
        connections_TF_peak = GRN@connections$TF_peaks[[as.character(perm)]]$connectionStats
        allTF = unique(connections_TF_peak$TF.ID) %>% as.character()
        nTF = ifelse(is.null(nPagesMax), length(allTF), min(length(allTF), nPagesMax - length(GRN@config$TF_peak_connectionTypes)))
        futile.logger::flog.info(paste0(" Including a total of ", nTF,  " TF. Preparing plots..."))
        
        # TODO: Check difference between TFActivity TFs and expression TFs
        
        pb <- progress::progress_bar$new(total = nTF)
        
        steps = GRN@config$parameters$internal$stepsFDR
        
        levels_pos <- unique(as.character(cut(steps, breaks = steps, right = FALSE, include.lowest = TRUE )))
        levels_neg <- unique(as.character(cut(steps, breaks = rev(steps), right = TRUE, include.lowest = TRUE )))
        
        
        TF_iteration = 0
        for (i in seq_len(nTF)) {
            
            TF_iteration = TF_iteration + 1
            pb$tick()
            
            if (!.print_TF(pages, TF_iteration, plotDetails)) {
                pageCounter = pageCounter + ifelse(plotDetails, 2, 1)
                next
            }
            
            TFCur = allTF[i]
            
            connections_TF_peakCur = dplyr::filter(connections_TF_peak, .data$TF.ID == TFCur)
            
            # Produce two FDR plots, coming from both directions
            plotsCur.l = list()
            for (typeCur in c("pos","neg")) {
                
                if (typeCur == "pos") {
                    levelsCur = levels_pos
                } else {
                    levelsCur = levels_neg
                }
                
                
                connections_TF_peak.filt = dplyr::filter(connections_TF_peakCur, .data$TF_peak.fdr_direction == typeCur ) %>%
                    dplyr::select(-"TF.ID", -"TF_peak.fdr_direction") %>%
                    dplyr::mutate(TF_peak.r_bin = factor(.data$TF_peak.r_bin, levels = levelsCur))  %>%
                    reshape2::melt(id = c("TF_peak.r_bin", "TF_peak.connectionType", "n")) 
                
                plotTitle = paste0(.printTF(GRN, TFCur, printEnsemblID = TRUE), ": Direction ", typeCur, ifelse(perm == 0, "", "(background)"))
                
                if (!useGCCorrection) {
                    
                    g = suppressWarnings(connections_TF_peak.filt %>% 
                                             dplyr::filter(.data$variable %in% c("TF_peak.fdr")) %>%
                                             ggplot2::ggplot(ggplot2::aes(.data$TF_peak.r_bin, .data$value, color = .data$n, shape = .data$variable)) +
                                             ggplot2::geom_point(na.rm = TRUE) +
                                             ggplot2::facet_wrap(~TF_peak.connectionType, ncol = 1) + 
                                             ggplot2::labs(x = "Correlation bin", y = "FDR TF-peak", title = plotTitle) +
                                             ggplot2::theme_bw() +
                                             ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 60, hjust = 1, size = 8)) +
                                             ggplot2::scale_x_discrete(drop = FALSE) + 
                                             ggplot2::scale_y_continuous(limits = c(0,1),minor_breaks = seq(0 , 1, 0.1), breaks = seq(0, 1, 0.1)) +
                                             ggplot2::scale_shape_manual("Background", 
                                                                         label = c("TF_peak.fdr" = "original"),
                                                                         values = c("TF_peak.fdr" = 1)) +
                                             ggplot2::scale_color_viridis_c("No. of connections") 
                    )
                    plotsCur.l[[typeCur]] = g
                    
                    
                    if (plotDetails) {
                        
                        
                        g2 = connections_TF_peak.filt %>%
                            dplyr::filter(.data$variable %in% c("n_tp", "n_fp", "n_fp_norm")) %>%
                            ggplot2::ggplot(ggplot2::aes(.data$TF_peak.r_bin, log10(.data$value + 1), color = .data$variable, group = .data$variable)) + 
                            ggplot2::geom_line() +
                            ggplot2::facet_wrap(~TF_peak.connectionType, ncol = 1) + 
                            ggplot2::labs(x = "Correlation bin", y = "log10(Observed frequencies +1)", title = plotTitle) +
                            ggplot2::theme_bw() +
                            ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 60, hjust = 1, size = 8)) +
                            ggplot2::scale_x_discrete(drop = FALSE) + 
                            ggplot2::geom_hline(yintercept = log10(1), linetype = "dotted", color = "gray") +
                            ggplot2::scale_color_manual("Observed data for\nFDR calculation", 
                                                        values = c("n_tp" = "black", 
                                                                   "n_fp" = "gray", 
                                                                   "n_fp_norm" = "darkred"),
                                                        labels = c("n_tp" = "True positives\n(foreground, scaling ref.)\n", 
                                                                   "n_fp" = "False positives\n(background, unscaled)\n", 
                                                                   "n_fp_norm" = "False positives\n(background, scaled to ref.)"))
                        
                        
                        plotsCur.l[[paste0(typeCur, "_details")]] = g2
                    }
                    
                    
                    
                } else {
                    
                    g = suppressWarnings(connections_TF_peak.filt %>% 
                                             dplyr::filter(stringr::str_starts(.data$variable, "TF_peak.fdr")) %>% 
                                             ggplot2::ggplot(ggplot2::aes(.data$TF_peak.r_bin, .data$value, color = .data$n, shape = .data$variable, alpha = .data$variable)) + 
                                             ggplot2::geom_point(na.rm = TRUE) +
                                             ggplot2::facet_wrap(~TF_peak.connectionType, ncol = 1) + 
                                             ggplot2::labs(x = "Correlation bin", y = "FDR TF-peak", title = plotTitle) +
                                             ggplot2::theme_bw() +
                                             ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 60, hjust = 1, size = 8)) +
                                             ggplot2::scale_x_discrete(drop = FALSE) + 
                                             ggplot2::scale_y_continuous(limits = c(0,1),minor_breaks = seq(0 , 1, 0.1), breaks = seq(0, 1, 0.1)) +
                                             ggplot2::scale_shape_manual("Background", 
                                                                         label = c("TF_peak.fdr" = "GC-adjusted","TF_peak.fdr_orig" = "original"),
                                                                         values = c("TF_peak.fdr" = 3,"TF_peak.fdr_orig" = 1)) +
                                             ggplot2::scale_color_viridis_c("No. of connections") + 
                                             ggplot2::scale_alpha_discrete(range = c("TF_peak.fdr" = 0.5,"TF_peak.fdr_orig" = 1))  +
                                             ggplot2::guides(alpha = FALSE)
                    )
                    plotsCur.l[[typeCur]] = g
                    
                    if (plotDetails) {
                        g2 = connections_TF_peak.filt %>%
                            dplyr::filter(.data$variable %in% c("n_tp", "n_fp", "n_fp_norm")) %>%
                            ggplot2::ggplot(ggplot2::aes(.data$TF_peak.r_bin, log10(.data$value + 1), color = .data$variable)) + 
                            ggplot2::geom_point(size = 1) +
                            ggplot2::facet_wrap(~TF_peak.connectionType, ncol = 1) + 
                            ggplot2::labs(x = "Correlation bin", y = "log10(Observed frequencies +1)", title = plotTitle) +
                            ggplot2::theme_bw() +
                            ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 60, hjust = 1, size = 8)) +
                            ggplot2::scale_x_discrete(drop = FALSE) + 
                            ggplot2::geom_hline(yintercept = log10(1), linetype = "dotted", color = "gray") +
                            ggplot2::scale_color_manual("Observed data for\nFDR calculation", 
                                                        values = c("n_tp" = "black", 
                                                                   "fpvalue_orig" = "lightgray",  
                                                                   "fpvalue_norm_orig" = "darkgray",
                                                                   "n_fp" = "brown1", 
                                                                   "fpvalue_norm" = "darkred"),
                                                        labels = c("n_tp" = "True positives\n(foreground, scaling ref.)\n", 
                                                                   "fpvalue_orig" = "False positives before GC\n(background, unscaled)\n",  
                                                                   "fpvalue_norm_orig" = "False positives without GC\n(background, scaled to ref.)\n",
                                                                   "n_fp" = "False positives with GC\n(background, unscaled)\n", 
                                                                   "fpvalue_norm" = "False positives with GC\n(background, scaled to ref.)"))
                        
                        plotsCur.l[[paste0(typeCur, "_details")]] = g2
                    }
                    
                }
                
            } # end for both directions 
            
            
            # PRINT PLOT
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                plots_all = plotsCur.l$pos / plotsCur.l$neg
                plot(plots_all)
                pageCounter = pageCounter + 1 
            }
            
            
            if (plotDetails) {
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    plots_all = plotsCur.l$pos_details / plotsCur.l$neg_details
                    plot(plots_all)
                    pageCounter = pageCounter + 1 
                }
                
            }
            
        } # end allTF
        
    } else {
        futile.logger::flog.info(paste0( "Skip TF-specific plots due to user-selected pages"))
    }
    
    .checkPageNumberValidity(pages, pageCounter)
    
    if (!is.null(file)) dev.off()
    
    .printExecutionTime(start)
}



#' Plot GC-specific diagnostic plots for TF-peak connections for a \code{\linkS4class{GRN}} object
#' 
#' Note: The arguments nTFMax and pages are not implemented yet
#' 
#' @inheritParams plotDiagnosticPlots_TFPeaks
#' @export
plotDiagnosticPlots_TFPeaks_GC <- function(GRN,
                                           outputFolder = NULL, 
                                           basenameOutput = NULL, 
                                           dataType = c("real", "background"),
                                           nTFMax = NULL,
                                           plotAsPDF = TRUE, pdf_width = 12, pdf_height_base = 15, pages = NULL,
                                           forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    
    # TODO: Implement nTFMax and pages
    
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    checkmate::assertSubset(dataType, c("real", "background"), empty.ok = FALSE)
    checkmate::assert(checkmate::checkNull(nTFMax), checkmate::checkIntegerish(nTFMax, lower = 1))
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height_base, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    
    useGCCorrection = GRN@config$parameters$useGCCorrection
    checkmate::assertFlag(useGCCorrection, na.ok = FALSE)
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    
    
    if (!GRN@config$parameters$useGCCorrection) {
        message = paste0("No GC correction was found in object. Make sure you run addConnections_TF_peak with useGCCorrection = TRUE")
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    } else {
        futile.logger::flog.info(paste0("Plotting GC diagnostic plots "))
    }
    
    dataType2 = c()
    if ("real" %in% dataType) dataType2 = c(0)
    if ("background" %in% dataType) dataType2 = c(dataType2, 1)
    
    
    for (permutationCur in 0:.getMaxPermutation(GRN)) {
        
        if (!permutationCur %in% dataType2) {
            next
        }
        
        suffixFile = .getPermutationSuffixStr(permutationCur)
        
        fileCur = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_TFPeak_fdr_GC"), basenameOutput), 
                         suffixFile)
        
        
        if (!file.exists(fileCur) | !plotAsPDF | forceRerun) {
            
            GRN = .addFunctionLogToObject(GRN)
            
            if (!plotAsPDF) fileCur = NULL
            
            heightCur = pdf_height_base * length(GRN@config$TF_peak_connectionTypes)
            .plot_TF_peak_fdr_GC(GRN, perm = as.character(permutationCur), 
                                 fileBasename = fileCur, pdf_width = pdf_width, pdf_height = heightCur,
                                 pages = pages) 
        }
        
    }
    
    .printExecutionTime(start, prefix = "")
    
    GRN
    
}


.plot_TF_peak_fdr_GC <- function(GRN, perm, fileBasename = NULL, pdf_width = 12, pdf_height = 15, pages = NULL) {
    
    for (TF_peak_connectionType_cur in GRN@config$TF_peak_connectionTypes) {
        
        if (!is.null(fileBasename)) {
            file = paste0(fileBasename, "_", TF_peak_connectionType_cur, ".pdf")
        } else {
            file = NULL
        }
        
        futile.logger::flog.info(paste0("Plotting GC diagnostic plots for TF-peak connections ", ifelse(is.null(file), "", paste0(" to file ", file)), 
                                        ". This may take a while."))
        
        if (!is.null(file)) {
            .checkOutputFile(file)
            grDevices::pdf(file = file, width = pdf_width, height = pdf_height)
        }
        
        ## Plots about GC background correction per TF
        for (TFCur in names(GRN@stats$GC$TFs_GC_correction_plots[[perm]] [[TF_peak_connectionType_cur]])) {
            plot(GRN@stats$GC$TFs_GC_correction_plots[[perm]] [[TF_peak_connectionType_cur]] [[TFCur]][[1]])
            plot(GRN@stats$GC$TFs_GC_correction_plots[[perm]] [[TF_peak_connectionType_cur]] [[TFCur]][[2]])
        }
        
        TFs_GC_correction = GRN@stats$GC$TFs_GC_correction[[perm]] [[TF_peak_connectionType_cur]] %>%
            dplyr::mutate(
                n.bg.needed.ratio = .data$n.bg / (.data$n.bg.needed + 0.1),
                n.bg.needed.ratio.atLeast1 = .data$n.bg.needed.ratio >= 1,
                diff_fg_bg = .data$n_rel.fg - .data$n_rel.bg,
                n.fg.bin = cut(.data$n.fg,  breaks = seq(0, max(.data$n.fg), 50), include.lowest = TRUE, dig.lab = 10)
            )
        
        # Cap ratio at max value
        ratioCap = 10
        TFs_GC_correction$n.bg.needed.ratio[which(TFs_GC_correction$n.bg.needed.ratio > ratioCap)] = ratioCap
        
        # Currently never the case due to the + 0.1 above
        TFs_GC_correction$n.bg.needed.ratio[which(is.nan(TFs_GC_correction$n.bg.needed.ratio))] = 1
        
        # Not useful, as minimum is 0 TFs_GC_correction$n.bg.needed.ratio[which(TFs_GC_correction$n.bg.needed.ratio < (1 / ratioCap))] = 1 / ratioCap
        
        ggplot(TFs_GC_correction, aes(.data$peak.GC.class, .data$n.fg.bin)) + geom_violin() + theme_bw()
        ggplot(TFs_GC_correction, aes(.data$peak.GC.class, .data$n.fg)) + geom_violin() + theme_bw()
        ggplot(TFs_GC_correction, aes(.data$peak.GC.class, .data$n.bg)) + geom_violin() + theme_bw()
        
        ggplot(TFs_GC_correction, aes(.data$peak.GC.class, .data$n.bg.needed.ratio)) + geom_violin() + 
            geom_hline(yintercept = 1, linetype = "dotted") + 
            ylab(paste0("n.bg.needed.ratio (capped at ", ratioCap, ")")) + 
            theme_bw()
        
        ggplot(TFs_GC_correction, aes(.data$peak.GC.class, fill = as.factor(.data$n.bg.needed.ratio.atLeast1))) + geom_bar() + 
            scale_fill_brewer("Background\nratio >=1", type = "div", palette = "Spectral")  + 
            theme_bw()
        
        TFs_GC_correction.melt =  TFs_GC_correction %>%
            dplyr::select("peak.GC.class", "n_rel.fg", "n_rel.bg") %>%
            reshape2::melt(id = "peak.GC.class")
        
        ggplot(TFs_GC_correction.melt, aes(.data$peak.GC.class, .data$value, color = .data$variable)) + 
            scale_color_brewer("Origin", label = c("n_rel.fg" = "foreground", "n_rel.bg" = "background"),  type = "div", palette = "Dark2") + ylab("Relative fraction") +
            geom_boxplot(outlier.size = 0.5) + theme_bw()
        
        ggplot(TFs_GC_correction, aes(.data$peak.GC.class, .data$diff_fg_bg)) + 
            ylim(-0.5,0.5) + 
            geom_violin() + theme_bw()
        
        ggplot(TFs_GC_correction, aes(.data$peak.GC.class, .data$diff_fg_bg)) + 
            ylim(-0.5,0.5) + 
            geom_violin() + theme_bw()
        
        
        ## HEATMAPS ON TF LEVEL ##
        
        var.transl = list(
            "n.fg" = list(
                label = "n\n(foreground)",
                col = circlize::colorRamp2(c(0, max(1,max(TFs_GC_correction$n.fg))), c("white", "red"))
            ),
            "peak_width_mean.fg" = list(
                label = "peak_width mean\n(foreground)",
                col = circlize::colorRamp2(c(0, max(1,max(TFs_GC_correction$peak_width_mean.fg, na.rm = TRUE))), c("white", "red"))
            ),
            "peak_width_sd.fg" = list(
                label = "peak_width sd\n(foreground)",
                col = circlize::colorRamp2(c(0, max(1,max(TFs_GC_correction$peak_width_sd.fg, na.rm = TRUE))), c("white", "red"))
            ),
            "n_rel.fg" = list(
                label = "n_rel\n(foreground)",
                col = circlize::colorRamp2(c(0, 1), c("white", "red"))
            ),
            "n.bg" = list(
                label = "n\n(background)",
                col = circlize::colorRamp2(c(0, max(1,max(TFs_GC_correction$n.bg, na.rm = TRUE))), c("white", "red"))
            ),
            "peak_width_mean.bg" = list(
                label = "peak_width mean\n(background)",
                col = circlize::colorRamp2(c(0, max(1,max(TFs_GC_correction$peak_width_mean.bg, na.rm = TRUE))), c("white", "red"))
            ),
            "peak_width_sd.bg" = list(
                label = "peak_width sd\n(background)",
                col = circlize::colorRamp2(c(0, max(1,max(TFs_GC_correction$peak_width_sd.bg, na.rm = TRUE))), c("white", "red"))
            ),
            "n_rel.bg" = list(
                label = "n_rel\n(background)",
                col = circlize::colorRamp2(c(0, 1), c("white", "red"))
            ),
            "n.bg.needed" = list(
                label = "n.needed\n(background)",
                col = circlize::colorRamp2(c(0, max(1,max(1,max(TFs_GC_correction$n.bg.needed, na.rm = TRUE)))), c("white", "red"))
            ),
            "n.bg.needed.ratio" = list(
                label = paste0("n.needed ratio\nCapped at ", ratioCap, "\n(background)"),
                col = circlize::colorRamp2(c(0, ratioCap), c("white", "red"))
            ),
            "n.bg.needed.ratio.atLeast1" = list(
                label = "n.needed.ratio.atLeast1\n(background)",
                col = c("black", "white")
            ),
            "diff_fg_bg" = list(
                label = "Difference\nrelative\nfrequency\n(foreground\nminus\nbackground)",
                col = circlize::colorRamp2(c(min(-1, -max(abs(TFs_GC_correction$diff_fg_bg), na.rm = TRUE)), 
                                             0, 
                                             max(1, max(abs(TFs_GC_correction$diff_fg_bg), na.rm = TRUE))), c("blue", "white", "red"))
            )
        )
        
        for (varCur in colnames(TFs_GC_correction)) {
            
            if (varCur %in% c("TF.ID", "peak.GC.class", "maxSizeBackground", "n.fg.bin")) next
            
            TFs_GC_correction.cur = TFs_GC_correction %>%
                dplyr::select("TF.ID", "peak.GC.class", {{varCur}}) %>%
                tidyr::pivot_wider(names_from = "peak.GC.class", values_from = {{varCur}}) %>% 
                dplyr::select("TF.ID", levels(TFs_GC_correction$peak.GC.class)) %>%
                dplyr::mutate(TF.ID = as.character(.data$TF.ID)) %>%
                dplyr::mutate_all(function(x) ifelse(is.nan(x) | is.infinite(x), NA, x)) %>%  # replaces NaN and Inf with NA
                # dplyr::select(dplyr::where(~sum(!is.na(.x)) > 0)) %>%
                tibble::column_to_rownames("TF.ID") %>% 
                as.matrix()
            
            # Remove columns with only NA
            
            ComplexHeatmap::Heatmap(
                TFs_GC_correction.cur * 1, # change into a numeric matrix in case it is a logical matrix
                name = var.transl[[varCur]]$label,
                cluster_columns = FALSE, cluster_rows = TRUE,
                row_names_side = "right", row_names_gp = grid::gpar(fontsize = 3), 
                col = var.transl[[varCur]]$col ,
                column_title = "GC adjustment details\nPeak GC bin",
                column_names_gp = grid::gpar(fontsize = 10),
                row_title = "TF (clustered)"
            ) %>% plot()
        }
        
        
        ## CHANGE the FDR bins
        bin.order.combined = .get_combined_TF_peak_bins(GRN)
        
        for (fdrCur in .getFDR_TF_peak_vector(GRN)) {
            GC.comp.df = GRN@connections$TF_peaks[[perm]]$main %>%
                dplyr::filter(.data$TF_peak.connectionType == TF_peak_connectionType_cur) %>%
                dplyr::mutate(TF_peak.r_bin = stringr::str_replace_all(.data$TF_peak.r_bin, stringr::fixed(")"), stringr::fixed("]")),
                              TF_peak.r_bin = stringr::str_replace_all(.data$TF_peak.r_bin, stringr::fixed("("), stringr::fixed("[")),
                              TF_peak.r_bin = factor(.data$TF_peak.r_bin, levels = bin.order.combined)) %>%
                dplyr::group_by(.data$TF.ID, .data$TF_peak.r_bin) %>%
                dplyr::summarise(n_beforeGC  = length(.data$TF_peak.fdr_orig[.data$TF_peak.fdr_orig <= fdrCur]),
                                 n_afterGC   = length(.data$TF_peak.fdr[.data$TF_peak.fdr <= fdrCur]),
                                 n_diff_abs  = .data$n_afterGC - .data$n_beforeGC,
                                 n_diff_rel  = .data$n_afterGC / .data$n_beforeGC) %>%
                dplyr::ungroup() %>%
                tidyr::complete(.data$TF.ID, .data$TF_peak.r_bin)
            
            
            # Change "Inf" values to a finite value
            maxValue = max(GC.comp.df$n_diff_rel[which(!is.infinite(GC.comp.df$n_diff_rel))], na.rm = TRUE)
            
            # Parameter to specify max rel difference for Inf values. Only needed when max otherwise is smaller than that, set to maximum otherwise   
            cap_n_diff_rel  = 10
            GC.comp.df$n_diff_rel[which(is.infinite(GC.comp.df$n_diff_rel))] = max(maxValue, cap_n_diff_rel)
            
            # 0 / 0 will be displayed as a ratio of 1 (no change)
            GC.comp.df$n_diff_rel[which(is.nan(GC.comp.df$n_diff_rel))] = 1
            
            
            var.transl = list(
                "n_beforeGC" = list(
                    label = "n\nbeforeGC",
                    col = circlize::colorRamp2(c(0, max(1, max(GC.comp.df$n_beforeGC, na.rm = TRUE))), c("white", "red"))
                ),
                "n_afterGC" = list(
                    label = "n\nafterGC",
                    col = circlize::colorRamp2(c(0, max(1, max(GC.comp.df$n_afterGC, na.rm = TRUE))), c("white", "red"))
                ),
                "n_diff_abs" = list(
                    label = "n\ndiff_abs",
                    col = circlize::colorRamp2(c(min(-1, -max(abs(GC.comp.df$n_diff_abs), na.rm = TRUE)), 
                                                 0, 
                                                 max(1, max(abs(GC.comp.df$n_diff_abs), na.rm = TRUE))), c("blue", "white", "red"))
                ),
                "n_diff_rel" = list(
                    label = "n\ndiff_rel",
                    col = circlize::colorRamp2(c(0, 
                                                 1, 
                                                 max(abs(GC.comp.df$n_diff_rel), na.rm = TRUE)), c("blue", "white", "red"))
                )
            )
            
            for (varCur in c("n_beforeGC", "n_afterGC", "n_diff_abs", "n_diff_rel")) {
                
                GC.comp.df %>%
                    dplyr::select("TF.ID", "TF_peak.r_bin", {{varCur}}) %>%
                    tidyr::pivot_wider(names_from = "TF_peak.r_bin", values_from = {{varCur}}) %>%
                    dplyr::select("TF.ID", tidyselect::any_of(bin.order.combined)) %>%
                    tibble::column_to_rownames("TF.ID") %>% 
                    as.matrix() %>%
                    ComplexHeatmap::Heatmap(
                        name = var.transl[[varCur]]$label,
                        cluster_columns = FALSE, cluster_rows = FALSE,
                        row_names_side = "right", row_names_gp = grid::gpar(fontsize = 3), 
                        col = var.transl[[varCur]]$col,
                        column_title = paste0("GRN connections (FDR = ", fdrCur, ")\nTF-peak correlation bin"),
                        column_names_gp = grid::gpar(fontsize = 10),
                        row_title = "TF (alphabetical, not clustered)"
                    ) %>% 
                    plot()
            }
            
        } # end for (fdrCur in .getFDR_TF_peak_vector(GRN)) {
        
        if (!is.null(file)) {
            grDevices::dev.off()
        }
        
        
    }  #end for each connection type
    
    
    
    # 
    # 
    # for (TF_peak_connectionType_cur in GRN@config$TF_peak_connectionTypes) {
    #     # TODO
    #     if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
    #         
    #         futile.logger::flog.info(paste0("Plotting GC diagnostic plots across all TF an specifically for each TF) ", ifelse(is.null(file), "", paste0(" to file ", file))))
    #         
    #         permIndex = as.character(perm)
    #         summary.df = GRN@connections$TF_peaks[[permIndex]]$connectionStats %>% dplyr::filter(.data$TF_peak.connectionType == TF_peak_connectionType_cur)
    #         
    #         g1 = ggplot2::ggplot(summary.df %>% dplyr::filter(.data$TF_peak.fdr_direction == "pos"), ggplot2::aes(.data$nForeground)) + 
    #             ggplot2::geom_histogram(bins = 100) +
    #             ggplot2::xlab("Number peaks in foreground") + 
    #             ggplot2::theme_bw()
    #         
    #         g2 = ggplot2::ggplot(summary.df %>% dplyr::filter(.data$TF_peak.fdr_direction == "pos"), ggplot2::aes(.data$nBackground_orig)) + 
    #             ggplot2::geom_histogram(bins = 100) +
    #             ggplot2::xlab("Number peaks in background (raw)") + 
    #             ggplot2::theme_bw()
    #         
    #         g3 = ggplot2::ggplot(summary.df %>% dplyr::filter(.data$direction == "pos"), ggplot2::aes(.data$nBackground)) + ggplot2::geom_histogram(bins = 100) +
    #             ggplot2::xlab("Number peaks in background (after GC-adjustment)") + 
    #             ggplot2::theme_bw()
    #         
    #         g4 = ggplot2::ggplot(summary.df %>% dplyr::filter(.data$direction == "pos"), ggplot2::aes(.data$ratio_fg_bg_orig)) + ggplot2::geom_histogram(bins = 100) +
    #             ggplot2::xlab("Ratio of foreground peaks / background peaks (raw)") + 
    #             ggplot2::theme_bw()
    #         
    #         g5 = ggplot2::ggplot(summary.df %>% dplyr::filter(.data$direction == "pos"), ggplot2::aes(.data$ratio_fg_bg)) + ggplot2::geom_histogram(bins = 100) +
    #             ggplot2::xlab("Ratio of foreground peaks / background peaks (after GC-adjustment)") + 
    #             ggplot2::theme_bw()
    #         
    #         g6 = ggplot2::ggplot(summary.df %>% dplyr::filter(.data$direction == "pos"), ggplot2::aes(.data$background_match_success)) + ggplot2::geom_histogram(stat = "count") +
    #             ggplot2::xlab("GC-background matching successful") + 
    #             ggplot2::theme_bw()
    #         
    #         g7 = ggplot2::ggplot(summary.df %>% dplyr::filter(.data$direction == "pos"), ggplot2::aes(.data$percBackgroundUsed)) + ggplot2::geom_histogram(stat = "count") +
    #             ggplot2::xlab("Percentage of used background for GC matching") +
    #             ggplot2::theme_bw()
    #         
    #         g8 = ggplot2::ggplot()
    #         
    #         # plots.l is nested, unnest 
    #         plots.mod.l = list()
    #         for (nameCur in names(GRN@stats$plots_GC)) {
    #             plots.mod.l[[paste0(nameCur,".rel")]] = GRN@stats$plots_GC[[nameCur]][[1]]
    #             plots.mod.l[[paste0(nameCur,".abs")]] = GRN@stats$plots_GC[[nameCur]][[2]]
    #         }
    #         
    #         
    #         plots_all.l = c(list(g1,g2,g3,g4,g5,g6,g7,g8), plots.mod.l)
    #         
    #         .printMultipleGraphsPerPage(plots_all.l, nCol = 1, nRow = 2, pdfFile = file, height = height, width = width)
    #         
    #         
    #     }
    #     # TODO
    #     pageCounter = pageCounter + 1
    #     
    # }  #end for each conenctionType
    
}




.print_TF <- function(pages, TF_iteration, plotDetails) {
    
    printTF = FALSE
    
    if (is.null(pages)) {
        printTF = TRUE
    }
    
    if (!plotDetails) {
        if (any(pages %in% (TF_iteration + 1))) {
            printTF = TRUE
        } 
    } else {
        if (any(pages %in% c(2 * TF_iteration, 2 * TF_iteration + 1))) {
            printTF = TRUE
        } 
    }
    
    
    printTF
}  


.generateTF_GC_diagnosticPlots <- function(TFCur, GC_classes_foreground.df, GC_classes_background.df, GC_classes_all.df, peaksForeground, peaksBackground, peaksBackgroundGC) {
    
    GC_classes_background_GC.df = peaksBackgroundGC %>%
        dplyr::group_by(.data$peak.GC.class) %>%
        dplyr::summarise(n =  dplyr::n(), peak_width_mean = mean(.data$peak.width), peak_width_sd = sd(.data$peak.width)) %>%
        dplyr::ungroup() %>% 
        tidyr::complete(.data$peak.GC.class, fill = list(n = 0)) %>%
        dplyr::mutate(n_rel = .data$n / nrow(peaksBackgroundGC), type = "background_GC")
    
    # TODO
    GC_classes_all2.df = rbind(GC_classes_foreground.df, GC_classes_background.df, GC_classes_background_GC.df) %>%
        dplyr::mutate(type = forcats::fct_relevel(.data$type, "foreground", "background_GC", "background_orig")) %>%
        dplyr::left_join(GC_classes_all.df, by = "peak.GC.class") %>%
        dplyr::rowwise() %>%
        dplyr::mutate(n.bg.needed.relFreq = dplyr::if_else(.data$n.bg.needed.ratio > 1, "", paste0(as.character(round(.data$n.bg.needed.ratio * 100, 0)),"%")))
    
    # Current hack: Put those numbers where the no. of peaks in the GC background < the required one (i.e., where resampling occured)
    GC_classes_all2.df$n.bg.needed.relFreq[GC_classes_all2.df$type != "background_GC" | GC_classes_all2.df$n == 0] = ""
    
    label_foreground = paste0("Foreground\n(", .prettyNum(nrow(peaksForeground)), " peaks)\n")
    label_background_orig = paste0("Background (raw)\n(", .prettyNum(nrow(peaksBackground)), " peaks)\n")
    label_background_GC = paste0("Background (GC-adj.)\n(", .prettyNum(nrow(peaksBackgroundGC)), " peaks)\n")
    
    labelVec = c("background_orig" = label_background_orig, "background_GC" = label_background_GC, "foreground" = label_foreground)
    colorVec = c("background_orig" = "lightgray", "background_GC" = "darkgray", "foreground" = "black")
    
    g1 = ggplot2::ggplot(GC_classes_all2.df , ggplot2::aes(.data$peak.GC.class, .data$n_rel, group = .data$type, fill = .data$type, label = .data$n.bg.needed.relFreq)) + 
        ggplot2::geom_bar(stat = "identity", position = ggplot2::position_dodge(width = 0.5), width = 0.5) + 
        ggplot2::geom_text(vjust = -0.5, size = 3)  + 
        ggplot2::ylim(0,0.75) + 
        #ggplot2::geom_line(ggplot2::aes(color= type), size = 2) +
        ggplot2::scale_fill_manual(name = "Peakset origin", labels  = labelVec, values = colorVec) + 
        #ggplot2::scale_color_manual(name = "Signal", labels = labelVec, values = colorVec) + 
        ggplot2::theme_bw() + 
        ggplot2::ggtitle(paste0(TFCur, ": Before and after GC-adjustment\n(Labeled bins: Resampling done, <100% was available)")) + 
        ggplot2::xlab("GC class from peaks") +
        ggplot2::ylab("Relative frequencies") +
        ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, size = 10)) 
    
    
    g2 = ggplot2::ggplot(GC_classes_all2.df , ggplot2::aes(.data$peak.GC.class, log10(.data$n + 1), group = .data$type, fill = .data$type)) + 
        ggplot2::geom_bar(stat = "identity", position = ggplot2::position_dodge(width = 0.5), alpha = 1, width = 0.5) + 
        #ggplot2::geom_line(ggplot2::aes(color= type), size = 2) +
        ggplot2::scale_fill_manual(name = "Peakset origin", labels  = labelVec, values = colorVec) + 
        #ggplot2::scale_color_manual(name = "Signal", labels = labelVec, values = colorVec) + 
        ggplot2::theme_bw() + ggplot2::ggtitle(paste0(TFCur, ": Before and after GC-adjustment")) + 
        ggplot2::xlab("GC class from peaks") +
        ggplot2::ylab("Absolute frequencies (log10 + 1)") +
        ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, size = 10)) 
    
    list(g1,g2)
    
}

.plotTF_peak_TFActivity_QC <- function(GRN, perm = 0, fileCur, width = 7, height = 8) {
    
    # TODO
}


.plot_TF_peak_connectionSummary <- function(GRN, fdr = c(0.001, 0.01, 0.05, 0.1, 0.2, 0.3), TF_peak_connectionType) {
    
    
    bin.order.combined = .get_combined_TF_peak_bins(GRN)
    
    ## COLLECT DATA ##
    data.l = list()
    for (permCur in c("0", "1")) {
        
        data.l[[permCur]] = GRN@connections$TF_peaks[[permCur]]$connectionStats %>%
            
            dplyr::mutate(TF_peak.r_bin = stringr::str_replace_all(.data$TF_peak.r_bin, stringr::fixed(")"), stringr::fixed("]")),
                          TF_peak.r_bin = stringr::str_replace_all(.data$TF_peak.r_bin, stringr::fixed("("), stringr::fixed("[")),
                          perm = permCur,
                          TF_peak.r_bin = factor(.data$TF_peak.r_bin, levels = bin.order.combined)
            )
    }
    
    data.all.df = data.table::rbindlist(data.l)
    
    futile.logger::flog.info(paste0( "TF-peak link summary for real vs background links, stratified by FDR"))
    
    data.summary.l = list()
    for (fdrCur in fdr) {
        
        data.summary.l[[as.character(fdrCur)]] = data.all.df %>%
            dplyr::filter(.data$TF_peak.fdr < fdrCur, .data$TF_peak.connectionType == TF_peak_connectionType) %>%
            dplyr::group_by(.data$TF_peak.r_bin, .data$perm, .drop = FALSE) %>%
            dplyr::summarise(n = sum(.data$n), nTFDistinct = dplyr::n_distinct(.data$TF.ID), 
                             TFDistinct = paste0(unique(.data$TF.ID), collapse = ","), .groups = "keep") %>%
            dplyr::ungroup() %>%
            dplyr::mutate(perm = factor(.data$perm, levels = c("0", "1")),
                          fdr = fdrCur) %>%
            dplyr::filter(!is.na(.data$perm)) %>%
            tidyr::complete(.data$TF_peak.r_bin, .data$perm,
                            fill = list(n = 0, nTFDistinct = 0, TFDistinct  = "", fdr = fdrCur),
                            explicit = FALSE)
        
        
        n_pos = data.summary.l[[as.character(fdrCur)]] %>%
            dplyr::filter(!stringr::str_starts(.data$TF_peak.r_bin, "\\[-"), ) %>%
            dplyr::group_by(.data$perm) %>%
            dplyr::summarise(sum = sum(.data$n), TF.ID = paste0(unique(.data$TFDistinct), collapse = ",")) %>%
            dplyr::arrange(.data$perm)
        
        n_neg = data.summary.l[[as.character(fdrCur)]] %>%
            dplyr::filter(stringr::str_starts(.data$TF_peak.r_bin, "\\[-"), ) %>%
            dplyr::group_by(.data$perm) %>%
            dplyr::summarise(sum = sum(.data$n), TF.ID = paste0(unique(.data$TFDistinct), collapse = ",")) %>%
            dplyr::arrange(.data$perm)
        
        distinct_TF_pos_0 = strsplit(n_pos$TF.ID[1], ",")[[1]] %>% unique()
        distinct_TF_pos_1 = strsplit(n_pos$TF.ID[2], ",")[[1]] %>% unique()
        distinct_TF_neg_0 = strsplit(n_neg$TF.ID[1], ",")[[1]] %>% unique()
        distinct_TF_neg_1 = strsplit(n_neg$TF.ID[2], ",")[[1]] %>% unique()
        
        futile.logger::flog.info(paste0( " FDR = ", fdrCur, "(real vs background)"))
        futile.logger::flog.info(paste0( "  Links total        : ", (n_pos$sum[1] + n_neg$sum[1]), " vs ", (n_pos$sum[2] +  n_neg$sum[1])))
        futile.logger::flog.info(paste0( "  Distinct TFs total : ", 
                                         (length(distinct_TF_pos_0) + length(distinct_TF_neg_0) - 2), " vs ",
                                         (length(distinct_TF_pos_1) + length(distinct_TF_neg_1) - 2 )))
        
        futile.logger::flog.info(paste0( "  Links with r>=0    : ", n_pos$sum[1], " vs ", n_pos$sum[2]))
        futile.logger::flog.info(paste0( "  Distinct TFs r>=0  : ", 
                                         length(distinct_TF_pos_0) - 1, " vs ", length(distinct_TF_pos_1) - 1))
        
        futile.logger::flog.info(paste0( "  Links with r<0     : " , n_neg$sum[1], " vs ", n_neg$sum[2]))
        futile.logger::flog.info(paste0( "  Distinct TFs r<0   : ", 
                                         length(distinct_TF_neg_0) - 1, " vs ", length(distinct_TF_neg_1) - 1))
        
    }
    
    data.df = data.table::rbindlist(data.summary.l)
    
    ## PLOT ##
    
    xAxiPos = c(1,seq(6,36,5),40)
    xAxisLabels = levels(data.df$TF_peak.r_bin)[xAxiPos]
    g = ggplot(data.df, aes(.data$TF_peak.r_bin, .data$n, fill = .data$perm, group = .data$perm)) +
        geom_col(position = position_dodge()) +
        xlab("TF-peak correlation bin") + ylab(paste0("No. of connections with FDR < ", fdrCur)) +
        facet_wrap(~fdr, ncol = 2, drop = FALSE, labeller = ggplot2::labeller(fdr = ggplot2::label_both)) +
        ggtitle(paste0("TF-peak summary for real & background links\nfor connection type ", TF_peak_connectionType)) + 
        scale_fill_manual("Background", values = c("1" = "red", "0" = "black"), labels = c("1" = "Yes", "0" = " No")) +
        scale_x_discrete(breaks = xAxisLabels, labels = xAxisLabels) +
        theme_bw() +
        theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 10), 
              axis.text.y = element_text(size = rel(0.8))) +
        # theme(panel.border = element_blank(), panel.grid.major = element_blank(),
        #       panel.grid.minor = element_blank(), axis.line = element_line(colour = "black"))
        theme(axis.line = element_line(colour = "black"))
    plot(g)
    
}


######### peak-gene QC ##########

#' Plot diagnostic plots for peak-gene connections for a \code{\linkS4class{GRN}} object
#'
#' @template GRN 
#' @template outputFolder
#' @template basenameOutput
#' @param gene.types List of character vectors. Default \code{list(c("all"), c("protein_coding"))}. Vectors of gene types to consider for the diagnostic plots. Multiple distinct combinations of gene types can be specified. For example, if set to \code{list(c("protein_coding", "lincRNA"), c("protein_coding"), c("all"))}, 3 distinct PDFs will be produced, one for each element of the list. The first file would only consider protein-coding and lincRNA genes, while the second plot only considers protein-coding ones. The special keyword "all" denotes all gene types found (usually, there are many gene types present, also more exotic and rare ones).
#' @param useFiltered Logical. \code{TRUE} or \code{FALSE}. Default \code{FALSE}. If set to \code{FALSE}, the diagnostic plots will be produced based on all peak-gene connections. This is the default and will usually be best to judge whether the background behaves as expected. If set to \code{TRUE}, the diagnostic plots will be produced based on the filtered set of connections. For this, the function \code{\link{filterGRNAndConnectGenes}} must have been run before.
#' @template plotDetails
#' @param plotPerTF Logical. \code{TRUE} or \code{FALSE}. Default \code{FALSE}. If set to \code{FALSE}, the diagnostic plots will be done across all TF (the default), while setting it to \code{TRUE} will generate the QC plots TF-specifically, including "all" TF, sorted by the number of connections.
#' @template plotAsPDF
#' @template pdf_width
#' @template pdf_height
#' @template pages
#' @template forceRerun
#' @return An updated \code{\linkS4class{GRN}} object.
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' types = list(c("protein_coding"))
#' GRN = plotDiagnosticPlots_peakGene(GRN, gene.types=types, plotAsPDF = FALSE, pages = 1)
#' @export
# TODO: implement forceRerun correctly
plotDiagnosticPlots_peakGene <- function(GRN, 
                                         outputFolder = NULL, 
                                         basenameOutput = NULL, 
                                         gene.types = list(c("all"), c("protein_coding")), 
                                         useFiltered = FALSE, 
                                         plotDetails = FALSE,
                                         plotPerTF = FALSE,
                                         plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12, pages = NULL,
                                         forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    GRN = .addFunctionLogToObject(GRN)
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    checkmate::assertList(gene.types, any.missing = FALSE, min.len = 1, types = "character")
    for (geneTypesCur in gene.types) {
        checkmate::assertSubset(geneTypesCur, c("all", unique(as.character(GRN@annotation$genes$gene.type))) %>% stats::na.omit(), empty.ok = FALSE)
    }  
    
    checkmate::assertFlag(useFiltered)
    checkmate::assertFlag(plotDetails)
    checkmate::assertFlag(plotPerTF)
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    
    
    # For compatibility reasons, re-create the genes and peaks annotation if not present in the object
    if (!"peak.annotation" %in% colnames(GRN@annotation$peaks)) {
        GRN = .populatePeakAnnotation(GRN)
    }
    if (!"gene.CV" %in% colnames(GRN@annotation$genes)) {
        GRN = .populateGeneAnnotation(GRN)
    }
    
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    if (is.null(GRN@connections$peak_genes[["0"]])) {
        message = paste0("Could not find peak-gene connections in GRN object. Run the function addConnections_peak_gene first")
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    }
    
    if (!plotAsPDF) {
        filenameCurBase = NULL
    } else {
        filenameCurBase = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_peakGene_diag"), basenameOutput), "_")
    }
    
    GRN = .plotDiagnosticPlots_peakGene_all(GRN, gene.types = gene.types, fileBase = filenameCurBase, 
                                            useFiltered = useFiltered, 
                                            plotPerTF = plotPerTF,
                                            pdf_width = pdf_width, pdf_height = pdf_height,
                                            plotDetails = plotDetails,
                                            pages = pages,
                                            forceRerun = forceRerun)
    
    .printExecutionTime(start, prefix = "")
    GRN
    
}
#' @import patchwork
#' @importFrom rlang .data
.plotDiagnosticPlots_peakGene_all <- function(GRN, gene.types,
                                              useFiltered = FALSE,
                                              plotDetails = FALSE,
                                              plotPerTF = FALSE,
                                              fileBase = NULL,
                                              pdf_width = 12,
                                              pdf_height = 12,
                                              pages = NULL,
                                              forceRerun = FALSE) {
    
    start = Sys.time()
    
    # get list of all filenames that are going to be created
    filenames.all = c()
    filteredStr = dplyr::if_else(useFiltered, "_filtered", "")
    for (geneTypesSelected in gene.types) {
        filenameCur = paste0(fileBase, paste0(geneTypesSelected, collapse = "+"), filteredStr, ".pdf")
        filenames.all = c(filenames.all, filenameCur)
    }
    
    if (!all(file.exists(filenames.all)) | forceRerun) {
        
        futile.logger::flog.info(paste0("Plotting diagnostic plots for peak-gene correlations", ifelse(is.null(fileBase), "", paste0(" to file(s) with basename ", fileBase))))
        
        cols_keep = c("peak.ID", "gene.ENSEMBL", "peak_gene.r", "peak_gene.p_raw", "peak_gene.distance")
        
        # Change from 10 to 5 
        nCategoriesBinning = 5
        probs = seq(0,1,1/nCategoriesBinning)
        
        
        range = GRN@config$parameters$promoterRange
        class_levels = c(paste0("real_",range), paste0("random_",range))
        networkType_details = c(paste0("real_",range), paste0("random_",range))
        
        colors_vec = c("black", "darkgray")
        networkType_vec = c("real", "background")
        names(colors_vec) = names(networkType_vec) = networkType_details
        
        options(dplyr.summarise.inform = FALSE) 
        
        #Either take all or the filtered set of connections
        if (useFiltered) {
            
            peakGeneCorrelations.all = 
                rbind(dplyr::select(GRN@connections$all.filtered[["0"]], dplyr::everything()) %>%
                          dplyr::mutate(class = paste0("real_",range))) %>%
                rbind(dplyr::select(GRN@connections$all.filtered[["1"]],  dplyr::everything()) %>% 
                          dplyr::mutate(class = paste0("random_",range)))
            
        } else {
            
            if (!"peak.GC.perc" %in% colnames(GRN@annotation$peaks)) {
                GRN@annotation$peaks$peak.GC.perc = NA
            }
            
            peakGeneCorrelations.all = 
                rbind(
                    dplyr::select(GRN@connections$peak_genes[["0"]], tidyselect::all_of(cols_keep)) %>%
                        dplyr::mutate(class = factor(paste0("real_",range), levels = class_levels)),
                    dplyr::select(GRN@connections$peak_genes[["1"]],  tidyselect::all_of(cols_keep)) %>% 
                        dplyr::mutate(class = factor(paste0("random_",range), levels = class_levels))) %>%
                dplyr::left_join(dplyr::select(GRN@annotation$genes, "gene.ENSEMBL", "gene.type", 
                                               "gene.mean", "gene.median", "gene.CV"), by = "gene.ENSEMBL", multiple = "all") %>%
                dplyr::left_join(GRN@annotation$peaks %>% 
                                     dplyr::select(-dplyr::starts_with("peak.gene."), -"peak.GC.perc"), by = "peak.ID") %>%
                dplyr::select(-"gene.ENSEMBL")
            
        }
        
        if (!plotPerTF) {
            peakGeneCorrelations.all = dplyr::select(peakGeneCorrelations.all, -"peak.ID")
        }
        
        nClasses_distance  = 10
        
        if (packageVersion("forcats") < "1.0.0") {
           
            peakGeneCorrelations.all = peakGeneCorrelations.all %>%
                dplyr::mutate(r_positive = .data$peak_gene.r > 0,
                              peak_gene.distance_class     = forcats::fct_explicit_na(addNA(cut(.data$peak_gene.distance, breaks = nClasses_distance, include.lowest = TRUE)), "random"),
                              peak_gene.distance_class_abs = forcats::fct_explicit_na(addNA(cut(abs(.data$peak_gene.distance), 
                                                                                                breaks = nClasses_distance, include.lowest = TRUE, ordered_result = TRUE)), "random"),
                )
        } else {
            
            # Use new functions to avoid depreciation warning
            peakGeneCorrelations.all = peakGeneCorrelations.all %>%
                dplyr::mutate(r_positive = .data$peak_gene.r > 0,
                              peak_gene.distance_class     = forcats::fct_na_value_to_level(addNA(cut(.data$peak_gene.distance, breaks = nClasses_distance, include.lowest = TRUE)), "random"),
                              peak_gene.distance_class_abs = forcats::fct_na_value_to_level(addNA(cut(abs(.data$peak_gene.distance), 
                                                                                                breaks = nClasses_distance, include.lowest = TRUE, ordered_result = TRUE)), "random"),
                )
        }
        
 
                          
        peakGeneCorrelations.all = peakGeneCorrelations.all %>%
              dplyr::mutate(peak_gene.p.raw.class = cut(.data$peak_gene.p_raw, breaks = seq(0,1,0.05), include.lowest = TRUE, ordered_result = TRUE),
                            peak_gene.r.class = cut(.data$peak_gene.r, breaks = seq(-1,1,0.05), include.lowest = TRUE, ordered_result = TRUE)) %>%
            dplyr::filter(!is.na(.data$peak_gene.r)) # Eliminate rows with NA for peak_gene.r. This can happen if the normalized gene counts are identical across ALL samples, and due to the lack of any variation, the correlation cannot be computed
        
        
        # Oddity of cut: When breaks is specified as a single number, the range of the data is divided into breaks pieces of equal length, and then the outer limits are moved away by 0.1% of the range to ensure that the extreme values both fall within the break intervals. 
        levels(peakGeneCorrelations.all$peak_gene.distance_class_abs)[1] = 
            gsub("(-\\d+)", "0", levels(peakGeneCorrelations.all$peak_gene.distance_class_abs)[1], perl = TRUE)
        
        # Prepare plots #
        
        colors_class = c("black", "black")
        names(colors_class) = unique(peakGeneCorrelations.all$class)
        colors_class[which(grepl("random", names(colors_class)))] = "darkgray"
        
        r_pos_class = c("black", "darkgray")
        names(r_pos_class) = c("TRUE", "FALSE")
        
        dist_class = c("dark red", "#fc9c9c")
        names(dist_class) = class_levels
        
        freqs = table(peakGeneCorrelations.all$class)
        freq_class = paste0(gsub(names(freqs), pattern = "(.+)(_.*)", replacement = "\\1"), " (n=", .prettyNum(freqs) , ")")
        # Change upstream and go with "background" everywhere
        freq_class = gsub(freq_class, pattern = "random", replacement = "background")
        names(freq_class) <- names(freqs)
        
        
        xlabels_peakGene_r.class = levels(peakGeneCorrelations.all$peak_gene.r.class)
        nCur = length(xlabels_peakGene_r.class)
        xlabels_peakGene_r.class[setdiff(seq_len(nCur), c(1, floor(nCur/2), nCur))] <- ""
        
        # For the last plot, which is wider, we label a few more
        xlabels_peakGene_r.class2 = levels(peakGeneCorrelations.all$peak_gene.r.class)
        nCur = length(xlabels_peakGene_r.class2)
        xlabels_peakGene_r.class2[setdiff(seq_len(nCur), c(1, floor(nCur/4), floor(nCur/2), floor(nCur/4*3), nCur))] <- ""
        
        xlabels_peakGene_praw.class = levels(peakGeneCorrelations.all$peak_gene.p.raw.class)
        nCur = length(xlabels_peakGene_praw.class)
        xlabels_peakGene_praw.class[setdiff(seq_len(nCur), c(1, floor(nCur/2), nCur))] <- ""
        
        #
        # ITERATE THROUGH ALL GENE TYPES, WITH ONE PER PLOT
        #
        for (geneTypesSelected in gene.types) {
            
            # Reset page counter for each PDF anew
            pageCounter = 1
            
            futile.logger::flog.info(paste0(" Gene type ", paste0(geneTypesSelected, collapse = "+")))
            
            if ("all" %in% geneTypesSelected) {
                indexCur = seq_len(nrow(peakGeneCorrelations.all))
            } else {
                indexCur = which(peakGeneCorrelations.all$gene.type %in% geneTypesSelected)
            }
            
            
            # START PLOTTING #
            
            if (!is.null(fileBase)) {
                filenameCur = paste0(fileBase, paste0(geneTypesSelected, collapse = "+"), filteredStr, ".pdf")
                .checkOutputFile(filenameCur)
                grDevices::pdf(file = filenameCur, width = pdf_width, height = pdf_height)
                
                futile.logger::flog.info(paste0(" Plotting to file ", filenameCur))
                
            }
            
            if (plotPerTF) {
                
                TF.nRows = rep(-1, length(GRN@config$allTF))
                #TF.nRows = rep(-1, 10)
                TF.peaks = list()
                names(TF.nRows) = GRN@config$allTF
                for (TFCur in GRN@config$allTF) {
                    TF.peaks[[TFCur]] = names(which(GRN@data$TFs$TF_peak_overlap[,TFCur] == 1))
                    TF.nRows[TFCur] = peakGeneCorrelations.all[indexCur,] %>% dplyr::filter(.data$peak.ID %in% TF.peaks[[TFCur]]) %>% nrow()
                }
                
                TFs_sorted = names(sort(TF.nRows, decreasing = TRUE))
                
                allTF = c("all", TFs_sorted)
                
            } else {
                allTF = "all"
            }
            
            counter = 0
            for (TFCur in allTF) {
                
                counter = counter + 1
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    if (length(allTF) > 1) {
                        futile.logger::flog.info(paste0(" QC plots for TF ", TFCur, " (", counter, " of ", length(allTF), ")"))
                    }
                    
                    
                    if ("all" %in% geneTypesSelected) {
                        indexCur = seq_len(nrow(peakGeneCorrelations.all))
                    } else {
                        indexCur = which(peakGeneCorrelations.all$gene.type %in% geneTypesSelected)
                    }
                    
                    
                    if (TFCur != "all") {
                        indexCur = intersect(indexCur, which(peakGeneCorrelations.all$peak.ID %in% TF.peaks[[TFCur]]))
                    }
                    
                    if (length(indexCur) == 0) {
                        message = "plotDiagnosticPlots_peakGene: No connections left after filtering and intersection, skip plots"
                        .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
                        next
                    }
                    
                    # Get subset also for just the real data
                    indexCurReal = intersect(indexCur, which(peakGeneCorrelations.all$class == names(dist_class)[1]))
                    
                    
                    xlabel = paste0("Correlation raw p-value")
                    
                    # DENSITY PLOTS P VALUE
                    
                    # TODO: Densities as ratio
                    # https://stackoverflow.com/questions/58629959/how-can-i-extract-data-from-a-kernel-density-function-in-r-for-many-samples-at-o#:~:text=To%20compute%20the%20density%20you,can%20use%20the%20package%20spatstat%20.
                    
                    # Produce the labels for the class-specific subtitles
                    customLabel_class = .customLabeler(table(peakGeneCorrelations.all[indexCur,]$class))
                    
                    r_pos_freq = table(peakGeneCorrelations.all[indexCur,]$r_positive)
                    labeler_r_pos = ggplot2::labeller(r_positive = c("TRUE"  = paste0("r positive (", r_pos_freq["TRUE"], ")"), 
                                                                     "FALSE" = paste0("r negative (", r_pos_freq["FALSE"], ")")) )
                    theme_main = ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 0, hjust = 0.5, size = ggplot2::rel(0.8)),
                                                axis.text.y = ggplot2::element_text(size = ggplot2::rel(0.8)),
                                                panel.grid.major = ggplot2::element_blank(), panel.grid.minor = ggplot2::element_blank())
                    
                    
                    
                    ## p-val density curves stratified by real vs background ##
                    
                    gA2 = ggplot2::ggplot(peakGeneCorrelations.all[indexCur,], ggplot2::aes(.data$peak_gene.p_raw, color = .data$r_positive)) + ggplot2::geom_density()  +
                        ggplot2::facet_wrap(~ .data$class, labeller = ggplot2::labeller(class = freq_class) ) +
                        ggplot2::xlab(xlabel) + ggplot2::ylab("Density") +  ggplot2::theme_bw() +
                        ggplot2::scale_color_manual(labels = names(r_pos_class), values = r_pos_class) +
                        ggplot2::theme(legend.position = "none", axis.text = ggplot2::element_text(size = ggplot2::rel(0.6)), strip.text.x = ggplot2::element_text(size = ggplot2::rel(0.8)))
                    
                    # Helper function to retrieve all tables and data aggregation steps for subsequent visualization
                    tbl.l = .createTables_peakGeneQC(peakGeneCorrelations.all[indexCur,], networkType_details, colors_vec, range)
                    
                    # Store in the GRN object for independent analysis
                    GRN@stats$peak_genes[[paste0(geneTypesSelected, collapse = "+")]] = tbl.l
                    
                    xlabel = paste0("Correlation raw\np-value (binned)")
                    
                    
                    xlabels = levels(tbl.l$d_merged$peak_gene.p.raw.class)
                    xlabels[setdiff(seq_len(length(xlabels)), c(1, floor(length(xlabels)/2), length(xlabels)))] <- ""
                    
                    gB3 = ggplot2::ggplot(tbl.l$d_merged, ggplot2::aes(.data$peak_gene.p.raw.class, .data$ratio, fill = .data$classAll)) + 
                        ggplot2::geom_bar(stat = "identity", position = "dodge", na.rm = TRUE, width = 0.5) + 
                        ggplot2::geom_hline(yintercept = 1, linetype = "dotted") + 
                        ggplot2::xlab(xlabel) + ggplot2::ylab("Ratio") +
                        ggplot2::scale_fill_manual("Class", values = c(dist_class, r_pos_class), 
                                                   labels = c("real", "background", "r+ (r>0)", "r- (r<=0)"), 
                        ) + # labels vector can be kind of manually specified here because the levels were previosly defined in a certain order
                        ggplot2::scale_x_discrete(labels = xlabels_peakGene_praw.class) +
                        ggplot2::theme_bw() +  
                        #ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, size = 8), strip.background = ggplot2::element_blank(), strip.placement = "outside", axis.title.y = ggplot2::element_blank()) +
                        # ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, size = 8) , axis.title.y = ggplot2::element_blank()) +
                        theme_main +
                        ggplot2::facet_wrap(~ factor(set), nrow = 2, scales = "free_y", strip.position = "left") 
                    
                    #  plot two FDR plots as well: (fraction of negative / negative+positive) and (fraction of background / background + real)
                    # that could give an indication of whether an FDR based on the background or based on the negative correlations would be more stringent
                    
                    # R PEAK GENE #
                    
                    xlabel = paste0("Correlation coefficient r")
                    
                    sum_real = table(peakGeneCorrelations.all[indexCur,]$class)[names(dist_class)[1]]
                    sum_rnd  = table(peakGeneCorrelations.all[indexCur,]$class)[names(dist_class)[2]]
                    binData.r = peakGeneCorrelations.all[indexCur,] %>%
                        dplyr::group_by(class) %>%
                        dplyr::count(.data$peak_gene.r.class) %>%
                        dplyr::mutate(nnorm = dplyr::case_when(class == !! (names(dist_class)[1]) ~ .data$n / (sum_real / sum_rnd), 
                                                               TRUE ~ as.double(.data$n)))
                    
                    xlabel = paste0("Correlation coefficient r (binned)")
                    
                    gD = ggplot2::ggplot(binData.r, ggplot2::aes(.data$peak_gene.r.class, .data$nnorm, group = .data$class, fill = .data$class)) + 
                        ggplot2::geom_bar(stat = "identity", position = ggplot2::position_dodge(preserve = "single"), na.rm = FALSE, width = 0.5) +
                        ggplot2::geom_line(ggplot2::aes(.data$peak_gene.r.class, .data$nnorm, group = .data$class, color = .data$class), stat = "identity") +
                        ggplot2::scale_fill_manual("Group", labels = names(dist_class), values = dist_class) +
                        ggplot2::scale_color_manual("Group", labels = names(dist_class), values = dist_class) +
                        ggplot2::scale_x_discrete(labels = xlabels_peakGene_r.class2, drop = FALSE) +
                        ggplot2::theme_bw() + ggplot2::theme(legend.position = "none") +
                        ggplot2::xlab(xlabel) + ggplot2::ylab("Abundance") +
                        theme_main  +
                        ggplot2::scale_y_continuous(labels = scales::scientific)
                    
                    
                    mainTitle = paste0("Summary QC (TF: ", TFCur, ", gene type: ", paste0(geneTypesSelected, collapse = "+"), ",\n", .prettyNum(range), " bp promoter range)")
                    
                    plots_all = ( ((gA2 | gB3 ) + 
                                       patchwork::plot_layout(widths = c(2.5,1.5))) / ((gD) + 
                                                                                           patchwork::plot_layout(widths = c(4))) ) + 
                        patchwork::plot_layout(heights = c(2,1), guides = 'collect') +
                        patchwork::plot_annotation(title = mainTitle, theme = ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5)))
                    
                    plot(plots_all)
                }
                pageCounter = pageCounter + 1
                
                
            } # end for each TF
            
            
            
            if (!is.null(GRN@annotation$peaks_obj) & is.installed("ChIPseeker")) {
                #plot(ChIPseeker::plotAnnoBar(GRN@annotation$peaks_obj))
                
                # no plot, as this is somehow just a list and no ggplot object
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    ChIPseeker::plotAnnoPie(GRN@annotation$peaks_obj, 
                                            main = paste0("\nPeak annotation BEFORE filtering (n = ", nrow(GRN@annotation$peaks), ")"))
                }
                pageCounter = pageCounter + 1
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    plot(ChIPseeker::plotDistToTSS(GRN@annotation$peaks_obj))
                }
                
                pageCounter = pageCounter + 1
            }
            
            # PEAK AND GENE PROPERTIES #
            
            xlabel = paste0("Correlation raw\np-value (binned)")
            mainTitle = paste0("Summary QC (TF: ", TFCur, ", gene type: ", paste0(geneTypesSelected, collapse = "+"), ", ", .prettyNum(range), " bp promoter range)")
            
            allVars = c("peak.annotation", "peak.GC.class", "peak.width", "peak.mean","peak.median",
                        "peak.CV", "gene.median",  "gene.mean", "gene.CV", "peak.gene.combined.CV")
            
            # Only use those actually available, as some packages may not be available
            allVars = intersect(allVars, colnames(peakGeneCorrelations.all))
            
            
            
            for (varCur in allVars) {
                #next
                # Save memory and prune the table and add only the variable we need here
                if (varCur != "peak.gene.combined.CV") {
                    dataCur = peakGeneCorrelations.all[indexCurReal,] %>%
                        dplyr::select("peak_gene.p_raw", tidyselect::all_of(varCur), "class", "r_positive", "peak_gene.p.raw.class", "peak_gene.distance") 
                } else {
                    dataCur = peakGeneCorrelations.all[indexCurReal,] %>%
                        dplyr::select("peak_gene.p_raw", "class", "gene.CV", "peak.CV", "r_positive", "peak_gene.p.raw.class", "peak_gene.distance")
                }
                
                
                # Choose colors depending on type of variable: gradient or not?
                
                if (varCur %in% c("peak.annotation","peak.GC.class")) {
                    
                    newColName = varCur
                    
                } else {
                    
                    newColName = paste0(varCur, ".class")
                    
                    if (varCur == "peak.gene.combined.CV") {
                        
                        dataCur = dataCur %>%
                            dplyr::mutate(!!(newColName) := dplyr::case_when(
                                gene.CV < 0.5                & peak.CV < 0.5                ~ "gene.CV+peak.CV<0.5", ##
                                # gene.CV >= 0.5 & gene.CV < 1 & peak.CV < 0.5                ~ "gene.CV<1_peak.CV<0.5",
                                # gene.CV >= 1                 & peak.CV < 0.5                ~ "gene.CV>1_peak.CV<0.5",
                                # 
                                # gene.CV < 0.5                & peak.CV >= 0.5 & peak.CV < 1 ~ "gene.CV<0.5_peak.CV<1",
                                # gene.CV >= 0.5 & gene.CV < 1 & peak.CV >= 0.5 & peak.CV < 1 ~ "gene.CV<1_peak.CV<1",
                                # gene.CV >= 1                 & peak.CV >= 0.5 & peak.CV < 1 ~ "gene.CV>1_peak.CV<1",
                                # 
                                # gene.CV < 0.5                & peak.CV >= 1                 ~ "gene.CV<0.5_peak.CV>1",
                                # gene.CV >= 0.5 & gene.CV < 1 & peak.CV >= 1                 ~ "gene.CV<1_peak.CV>1",
                                gene.CV >= 1                 & peak.CV >= 1                 ~ "gene.CV+peak.CV>1", ##
                                TRUE ~ "other"
                            )) %>%
                            dplyr::select(-"gene.CV", -"peak.CV")
                        
                    } else {
                        
                        if (dplyr::n_distinct(dataCur %>% dplyr::pull(.data[[varCur]])) > 1) {
                            dataCur = dataCur %>%
                                dplyr::mutate(!!(newColName) := cut(.data[[varCur]], breaks = unique(quantile(.data[[varCur]], probs = probs, na.rm = TRUE)), 
                                                                    include.lowest = TRUE, ordered_result = TRUE))
                            
                        } else {
                            dataCur = dplyr::mutate(dataCur, !!(newColName) := as.factor(.data[[varCur]]))
                        }
                        
                        dataCur = dplyr::select(dataCur, -tidyselect::all_of(varCur))
                        
                        
                    }
                    
                    
                }
                
                # Filter groups with fewer than 100 observations
                nGroupsMin = 100
                dataCur = dataCur %>%
                    dplyr::group_by(.data[[newColName]]) %>%  
                    dplyr::filter(dplyr::n() >= nGroupsMin) %>%
                    dplyr::ungroup()
                
                if (nrow(dataCur) == 0) {
                    message = paste0("plotDiagnosticPlots_peakGene: For variable ", varCur, ", not enough data left after filtering to plot, skipping")
                    .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
                    next
                }
                
                var.label = .prettyNum(table(dataCur[, newColName]))
                var.label = paste0(names(var.label), "\n(n=", var.label , ")\n")
                
                # Set colors
                if (varCur != "peak.annotation") {
                    mycolors <- viridis::viridis(length(var.label))
                } else {
                    # Only here, we want to have colors that are not a gradient
                    mycolors = var.label
                    downstream  = which(grepl("downstream", var.label, ignore.case = TRUE))
                    promoter    = which(grepl("promoter", var.label, ignore.case = TRUE))
                    
                    # Remove the first, white-like color from the 2 palettes
                    # Also make sure to always select at least 3 colors to avoid a warning
                    mycolors[downstream] = RColorBrewer::brewer.pal(max(2, length(downstream)) + 1, "Greens")[-1][seq_len(length(downstream))]
                    mycolors[promoter]   = RColorBrewer::brewer.pal(max(2, length(promoter)) + 1, "Purples")[-1][seq_len(length(promoter))]
                    mycolors[which(grepl("3' UTR", mycolors))] = "yellow"
                    mycolors[which(grepl("5' UTR", mycolors))] = "orange"
                    mycolors[which(grepl("Distal Intergenic", mycolors))] = "red"
                    mycolors[which(grepl("Exon", mycolors))] = "maroon"
                    mycolors[which(grepl("Intron", mycolors))] = "lightblue"
                    
                }
                
                r_pos_tbl = dataCur %>% dplyr::group_by(.data$r_positive) %>% dplyr::pull(.data$r_positive) %>% table()
                
                # In case the table contains only either TRUE or FALSE, we have to
                r_positive_label = c("TRUE"   = paste0("r+(r>0, n=", .prettyNum(ifelse(is.na(r_pos_tbl["TRUE"]), 0, r_pos_tbl[["TRUE"]])), ")"), 
                                     "FALSE" = paste0("r-(r<=0, n=" ,.prettyNum(ifelse(is.na(r_pos_tbl["FALSE"]), 0, r_pos_tbl[["FALSE"]])), ")"))
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    
                    # Class in the ggplot2::facet_wrap has been removed, as this is only for real data here
                    gA3 = ggplot2::ggplot(dataCur, ggplot2::aes(.data$peak_gene.p_raw, color = .data[[newColName]])) + ggplot2::geom_density(linewidth = 0.2)  +
                        ggplot2::facet_wrap(~.data$r_positive, labeller = ggplot2::labeller(r_positive = r_positive_label), nrow = 2) +
                        ggplot2::geom_density(ggplot2::aes(color = .data$classNew), color = "black",  linetype = "dotted", alpha = 1) + 
                        ggplot2::xlab(xlabel) + ggplot2::ylab("Density (for real data only)") +  ggplot2::theme_bw() +
                        ggplot2::scale_color_manual(newColName, values = mycolors, labels = var.label, drop = FALSE ) +
                        ggplot2::theme(axis.text = ggplot2::element_text(size = ggplot2::rel(0.6)), strip.text.x = ggplot2::element_text(size = ggplot2::rel(0.8)), 
                                       legend.text = ggplot2::element_text(size = ggplot2::rel(0.6)), legend.position = "none")
                    
                    # Ratios for r+ / r-
                    freq =  dataCur %>%
                        dplyr::group_by(class, .data[[newColName]], .data$peak_gene.p.raw.class, .data$r_positive) %>%
                        dplyr::summarise(n = dplyr::n()) %>%
                        dplyr::ungroup() %>%
                        tidyr::complete(class, .data[[newColName]], .data$peak_gene.p.raw.class, .data$r_positive, fill = list(n = 0)) %>% # SOme cases might be missing
                        dplyr::group_by(class, .data[[newColName]], .data$peak_gene.p.raw.class) %>% # dont group by r_positive because we want to calculate the ratio within each group
                        dplyr::filter(class == names(dist_class)[1]) %>% # Keep only one r_positive row per grouping as we operate via the ratio and this data is duplicated otherwise. Remove random data also because these have been filtered out before are only back due to the complete invokation.
                        dplyr::mutate(n = .data$n + 1) # to allow ratios to be computed even for 0 counts
                    
                    if (length(which(freq$r_positive)) > 0 && length(which(!freq$r_positive)) > 0) {
                        freq = freq %>%
                            dplyr::mutate(ratio_pos_raw = .data$n[.data$r_positive] / .data$n[!.data$r_positive])
                    } else {
                        freq = freq %>%
                            dplyr::mutate(ratio_pos_raw = NA)
                    }
                    
                    # Keep only one r_positive row per grouping as we operate via the ratio and this data is duplicated otherwise. Remove random data also because these have been filtered out before are only back due to the complete invokation.
                    freq = freq %>% dplyr::filter(.data$r_positive, class == names(dist_class)[1]) 
                    
                    
                    # Cap ratios > 10 at 10 to avoid visual artefacts
                    freq$ratio_pos_raw[which(freq$ratio_pos_raw > 10)] = 10
                    
                    # Without proper colors for now, this will be added after the next plot
                    gB3 = ggplot2::ggplot(freq, ggplot2::aes(.data$peak_gene.p.raw.class, .data$ratio_pos_raw, fill = .data[[newColName]])) + 
                        ggplot2::geom_bar(stat = "identity", position = "dodge", na.rm = TRUE, width = 0.8) + 
                        ggplot2::geom_hline(yintercept = 1, linetype = "dotted") + 
                        ggplot2::xlab(xlabel) + ggplot2::ylab("Ratio r+ / r- (capped at 10)") +
                        ggplot2::scale_x_discrete(labels = xlabels_peakGene_praw.class) +
                        ggplot2::scale_fill_manual(varCur, values = mycolors, labels = var.label, drop = FALSE )  +
                        ggplot2::theme_bw() +  
                        #ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, size = 8), strip.background = ggplot2::element_blank(), strip.placement = "outside", axis.title.y = ggplot2::element_blank()) +
                        # ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, size = 8) , axis.title.y = ggplot2::element_blank()) +
                        theme_main +
                        ggplot2::facet_wrap(~ factor(class), nrow = 2, scales = "free_y", strip.position = "left", labeller = ggplot2::labeller(class = freq_class)) 
                    
                    
                    plots_all = ( ((gA3 | gB3 ) + 
                                       patchwork::plot_layout(widths = c(2.5,1.5))) ) + 
                        patchwork::plot_layout(guides = 'collect') +
                        patchwork::plot_annotation(title = mainTitle, theme = ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5)))
                    
                    plot(plots_all)
                }
                pageCounter = pageCounter + 1
                
                
                # VERSION JUDITH: simplified peak.gene distance + another variable as histogram, no background data
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    datasetName = ""
                    if (!is.null(GRN@config$metadata$name)) {
                        datasetName = GRN@config$metadata$name
                    }
                    
                    mainTitle2 = paste0(datasetName, "\nSummary QC (TF: ", TFCur, ", gene type: ", paste0(geneTypesSelected, collapse = "+"), ", ",
                                        .prettyNum(range), " bp promoter range, stratified by distance + ", varCur, ")")
                    
                    # r+ and r-
                    binwidth = 0.1
                    mycolors <- viridis::viridis(2)
                    xlabel = paste0("Correlation raw p-value (", binwidth, " bins)")
                    
                    dataCur = dataCur %>%
                        dplyr::mutate(peak_gene.distance.class250k = factor(dplyr::if_else(.data$peak_gene.distance <= 250000, "<=250k", ">250k"))) %>%
                        dplyr::select(-"peak_gene.distance")
                    
                    # closed = "left", boundary = 0, ensure correct numbers. See https://github.com/tidyverse/ggplot2/issues/1739
                    # Without boundary = 0, counts are actually wrong
                    
                    
                    nrows_plot = 2
                    if (dplyr::n_distinct(dataCur[[newColName]]) > 9) {
                        nrows_plot = 3
                    }
                    
                    gA5 = ggplot2::ggplot(dataCur, ggplot2::aes(.data$peak_gene.p_raw, fill = .data$r_positive)) + 
                        ggplot2::geom_histogram(binwidth = binwidth, position = "dodge", closed = "left", boundary = 0)  +
                        ggplot2::facet_wrap(~ peak_gene.distance.class250k  + .data[[newColName]], nrow = nrows_plot, scales = "free_y") +
                        ggplot2::xlab(xlabel) + ggplot2::ylab(paste0("Abundance for classes with n>=", nGroupsMin)) +  ggplot2::theme_bw() +
                        ggplot2::ggtitle(mainTitle2) + 
                        ggplot2::scale_fill_manual("Class for r", values = mycolors, labels = r_positive_label, drop = FALSE ) +
                        ggplot2::theme(axis.text = ggplot2::element_text(size = ggplot2::rel(0.6)), strip.text.x = ggplot2::element_text(size = ggplot2::rel(0.6)), 
                                       legend.text = ggplot2::element_text(size = ggplot2::rel(0.7)))
                    
                    
                    plot(gA5)
                }
                pageCounter = pageCounter + 1
                
                
                
            } #end for each variable
            
            
            # DISTANCE FOCUSED #
            
            
            # Here, we focus on distance and exclude distance classes with too few points and create a new subset of the data
            
            # Filter distance classes with too few points
            distance_class_abund = table(peakGeneCorrelations.all[indexCur,]$peak_gene.distance_class_abs)
            indexFilt = which(peakGeneCorrelations.all$peak_gene.distance_class_abs %in% 
                                  names(distance_class_abund)[which(distance_class_abund > 50)])
            indexFilt = intersect(indexFilt, indexCur)
            
            if (length(indexFilt) > 0) {
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    g = ggplot2::ggplot(peakGeneCorrelations.all[indexFilt,], ggplot2::aes(.data$peak_gene.p_raw, color = .data$peak_gene.distance_class_abs)) + ggplot2::geom_density() + 
                        ggplot2::ggtitle(paste0("Density of the raw p-value distributions")) + 
                        ggplot2::facet_wrap(~ r_positive, ncol = 2, labeller = labeler_r_pos) + 
                        ggplot2::scale_color_viridis_d(labels = .classFreq_label(table(peakGeneCorrelations.all[indexFilt,]$peak_gene.distance_class_abs))) +
                        ggplot2::theme_bw()
                    plot(g)
                }
                pageCounter = pageCounter + 1
                
                
            } # end if (length(indexFilt) > 0)
            
            
            
            
            if (length(indexFilt) > 0) {
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    g = ggplot2::ggplot(peakGeneCorrelations.all[indexFilt,], ggplot2::aes(.data$peak_gene.p_raw, color = .data$r_positive)) + 
                        ggplot2::geom_density() + 
                        ggplot2::ggtitle(paste0("Density of the raw p-value distributions")) + 
                        ggplot2::facet_wrap(~ peak_gene.distance_class_abs,  ncol = 2, labeller = .customLabeler(table(peakGeneCorrelations.all$peak_gene.distance_class_abs))) +
                        ggplot2::scale_color_manual(labels = .classFreq_label(table(peakGeneCorrelations.all[indexFilt,]$r_positive)), values = r_pos_class) +
                        ggplot2::theme_bw()
                    plot(g)
                }
                pageCounter = pageCounter + 1
                
                
            }
            
            
            #
            # Focus on peak_gene.r
            #
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                
                g = ggplot2::ggplot(peakGeneCorrelations.all[indexCur,], ggplot2::aes(.data$peak_gene.r, color = .data$peak_gene.distance_class_abs)) + ggplot2::geom_density() + 
                    ggplot2::geom_density(data = peakGeneCorrelations.all[indexCur,], ggplot2::aes(.data$peak_gene.r), color = "black") + 
                    ggplot2::ggtitle(paste0("Density of the correlation coefficients")) + 
                    ggplot2::scale_color_viridis_d(labels = .classFreq_label(table(peakGeneCorrelations.all[indexCur,]$peak_gene.distance_class_abs))) +
                    ggplot2::theme_bw()
                plot(g)
            }
            pageCounter = pageCounter + 1
            
            .checkPageNumberValidity(pages, pageCounter)
            if (!is.null(fileBase)) {
                grDevices::dev.off()
            }
            
            
        }
        
    } else {
        futile.logger::flog.info(paste0("All output files already exist. Set forceRerun = TRUE to regenerate and overwrite."))
        
    }
    
    
    
    .printExecutionTime(start)
    
    GRN
    
}



.customLabeler <- function(tbl_freq) {
    tbl_freq_label = paste0(names(tbl_freq), " (", tbl_freq, ")")
    names(tbl_freq_label) = names(tbl_freq)
    ggplot2::as_labeller(tbl_freq_label)
}




###### Connection summaries ########

#' Plot various network connectivity summaries for a \code{\linkS4class{GRN}} object
#'
#' @template GRN 
#' @param type Character. Either \code{"heatmap"} or \code{"boxplot"}. Default \code{"heatmap"}. Which plot type to produce?
#' @template outputFolder
#' @template basenameOutput
#' @template plotAsPDF
#' @template pdf_width
#' @template pdf_height
#' @template pages
#' @template forceRerun
#' @return The same \code{\linkS4class{GRN}} object, without modifications. 
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plot_stats_connectionSummary(GRN, forceRerun = FALSE, plotAsPDF = FALSE, pages = 1)
#' @export
#' @importFrom circlize colorRamp2
plot_stats_connectionSummary <- function(GRN, type = "heatmap", 
                                         outputFolder = NULL, basenameOutput = NULL, 
                                         plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12, pages = NULL,
                                         forceRerun = FALSE) {
    
    start = Sys.time()     
    checkmate::assertClass(GRN, "GRN")
    GRN = .addFunctionLogToObject(GRN)
    
    GRN = .makeObjectCompatible(GRN)
    
    
    checkmate::assertChoice(type, c("heatmap", "boxplot", "density"))
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    if (type ==  "heatmap") {
        if (plotAsPDF) {
            file = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_connectionSummary_heatmap"), basenameOutput), ".pdf")
        } else {
            file = NULL
        }
        
        .plot_stats_connectionSummaryHeatmap(GRN, file = file, pdf_width = pdf_width, pdf_height = pdf_height, pages = pages, forceRerun = forceRerun) 
        
    } else if (type ==  "boxplot") {
        
        if (plotAsPDF) {
            file = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_connectionSummary_boxplot"), basenameOutput), ".pdf")
        } else {
            file = NULL
        }
        
        .plot_stats_connectionSummaryBoxplot(GRN, file = file, pdf_width = pdf_width, pdf_height = pdf_height, pages = pages, forceRerun = forceRerun) 
        
    } else if (type ==  "density") {
        
        stop("Not yet implemented")
        if (plotAsPDF) {
            file = "TODO"
        } else {
            file = NULL
        }
        .plot_stats_connectionSummaryDensity(GRN, file = file, pdf_width = pdf_width, pdf_height = pdf_height, pages = pages, forceRerun = forceRerun) 
    }
    
    .printExecutionTime(start, prefix = "")
    GRN
    
}


.plot_stats_connectionSummaryHeatmap <- function(GRN, file = NULL, pdf_width = 12, pdf_height = 12, pages = NULL, forceRerun = FALSE) {
    
    start = Sys.time()
    
    futile.logger::flog.info(paste0("Plotting connection summary", ifelse(is.null(file), "", paste0(" to file ", file))))
    
    if ((!is.null(file) && !file.exists(file)) | is.null(file) | forceRerun) {
        
        if (nrow(GRN@stats$connections) == 0) {
            message = paste0("Statistics summary missing from object, please run the function generateStatsSummary first")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
        }
        
        index = 0
        
        
        if (!is.null(file)) {
            .checkOutputFile(file)
            grDevices::pdf(file = file, width = pdf_width, height = pdf_height)
        }
        
        pageCounter = 1 
        for (allowMissingTFsCur in unique(GRN@stats$connections$allowMissingTFs)) {
            
            for (allowMissingGenesCur in unique(GRN@stats$connections$allowMissingGenes)) {
                
                for (TF_peak.connectionTypeCur in GRN@config$TF_peak_connectionTypes) {
                    
                    # Plot a summary page with the parameters used for the next page
                    # 
                    #index = index + 1
                    
                    for (elemCur in c("nTFs", "nPeaks", "nGenes")) {
                        
                        plotData.l = list()
                        for (permCur in 0:.getMaxPermutation(GRN)) {
                            
                            permIndex = as.character(permCur)
                            
                            stats_filtered.df = GRN@stats$connections %>%
                                dplyr::filter(.data$allowMissingGenes == allowMissingGenesCur, 
                                              .data$allowMissingTFs == allowMissingTFsCur,
                                              .data$TF_peak.connectionType == TF_peak.connectionTypeCur, 
                                              is.na(.data$peak_gene.p_raw), 
                                              .data$perm == permCur) %>%
                                dplyr::select("TF_peak.fdr", "peak_gene.fdr", "nGenes", "nTFs", "nPeaks")
                            
                            # Stratify by TF, peak or gene and produce a simple matrix
                            if (nrow(stats_filtered.df) > 0) {
                                plotData.l[[permIndex]] = as.matrix(reshape2::dcast(stats_filtered.df, TF_peak.fdr ~ peak_gene.fdr, value.var = elemCur)[,-1])
                                
                                colnames(plotData.l[[permIndex]]) = paste0("peak_gene\n.fdr_", colnames(plotData.l[[permIndex]]))
                                rownames(plotData.l[[permIndex]]) = paste0("TF_peak\n.fdr_", sort(unique(stats_filtered.df$TF_peak.fdr)))
                            }
                            
                            
                        }
                        
                        # Now we have the data for both permutations, let's plot
                        
                        maxDataRange = max(unlist(lapply(plotData.l,FUN = max))) * 1.1
                        
                        for (permCur in 0:.getMaxPermutation(GRN)) {
                            
                            index = index + 1
                            permIndex = as.character(permCur)
                            permSuffix = paste0(dplyr::if_else(permCur == 0, " (real)", " (background)"))
                            
                            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                                
                                titlePlot =  paste0("Number of unique ", gsub("^n", "", elemCur), permSuffix,
                                                    "\nallowMissingTFs: ", allowMissingTFsCur, 
                                                    ", allowMissingGenes: ", allowMissingGenesCur, 
                                                    ",\n TF_peak.connectionType: ", TF_peak.connectionTypeCur)
                                
                                # TOOO PACKAGES: Replace by something else?
                                colors = circlize::colorRamp2(c(0, maxDataRange), c("white", "red"))
                                
                                ComplexHeatmap::Heatmap(
                                    plotData.l[[permIndex]],
                                    name = "Number of\nconnections",
                                    col = colors,
                                    cluster_columns = FALSE, cluster_rows = FALSE,
                                    row_names_side = "right", row_names_gp = grid::gpar(fontsize = 10), 
                                    column_title = titlePlot,
                                    column_names_gp = grid::gpar(fontsize = 10),
                                    row_title = "TF-peak FDR",
                                    cell_fun = function(j, i, x, y, width, height, fill) {
                                        grid::grid.text(sprintf("%.0f", plotData.l[[permIndex]][i, j]), x, y, gp = grid::gpar(fontsize = 20))
                                    }
                                ) %>% plot()
                                
                            }
                            pageCounter = pageCounter + 1
                            
                        }
                        
                        
                    } # end for (elemCur in c("nTFs", "nPeaks", "nGenes"))
                    
                } # end for (TF_peak.connectionTypeCur in TF_peak.connectionTypesAll)
            }
        }
        
        .checkPageNumberValidity(pages, pageCounter)
        if (!is.null(file)) {
            grDevices::dev.off()
        }
        
        
    }
    
    .printExecutionTime(start)
    
}

.plot_stats_connectionSummaryDensity <- function(GRN, file = file, TF_peak.connectionType, allowMissingTFs = FALSE, allowMissingGenes = FALSE, pdf_width = 12, pdf_height = 12, pages = NULL, forceRerun = FALSE) {
    
    # TODO
    # Same for both permutations, in one plot
    main.df = GRN@connections$TF_peaks[["0"]]$main
    main.filt.df = dplyr::filter(main.df, .data$TF_peak.fdr < 0.3)
    ggplot2::ggplot(main.df, ggplot2::aes(.data$TF_peak.fdr)) + ggplot2::geom_density() + ggplot2::theme_bw()
    ggplot2::ggplot(main.df, ggplot2::aes(.data$TF_peak.fdr)) + ggplot2::geom_histogram(binwidth = 0.01) +  
        ggplot2::geom_vline(xintercept = c(0.001, 0.01, 0.05, 0.1, 0.2, 0.3), linetype = "dotted", color = "darkgray") + ggplot2::theme_bw()
    ggplot2::ggplot(main.filt.df, ggplot2::aes(.data$TF_peak.fdr)) + ggplot2::geom_histogram(binwidth = 0.01) +
        ggplot2::geom_vline(xintercept = c(0.001, 0.01, 0.05, 0.1, 0.2, 0.3), linetype = "dotted", color = "darkgray") + ggplot2::theme_bw()
    
    
    # Connectivity of TF-peaks in dependency of TF_peak.fdr
    
    
}


# Compare real and background one, replicate Darias plots
.plot_stats_connectionSummaryBoxplot <- function(GRN, file = NULL, pdf_width = 12, pdf_height = 12, pages = NULL, forceRerun = FALSE) {
    
    start = Sys.time()
    
    if ((!is.null(file) && !file.exists(file)) | is.null(file) | forceRerun) {
        
        
        futile.logger::flog.info(paste0("Plotting diagnostic plots for network connections", ifelse(is.null(file), "", paste0(" to file ", file))))
        
        
        if (!is.null(file)) {
            .checkOutputFile(file)
            grDevices::pdf(file = file, width = pdf_width, height = pdf_height)
        }
        
        stats_details.l = GRN@stats$connectionDetails.l
        
        TF_peak.fdrs      = names(GRN@stats$connectionDetails.l[["0"]])
        peak_gene.fdrs    = names(GRN@stats$connectionDetails.l[["0"]][[TF_peak.fdrs[1]]])
        allowMissingTFs   = names(GRN@stats$connectionDetails.l[["0"]][[TF_peak.fdrs[1]]][[peak_gene.fdrs[1]]])
        allowMissingGenes = names(GRN@stats$connectionDetails.l[["0"]][[TF_peak.fdrs[1]]][[peak_gene.fdrs[1]]][[allowMissingTFs[1]]])
        
        
        nElemsTotal = length(TF_peak.fdrs) * length(peak_gene.fdrs) * length(allowMissingTFs) * length(allowMissingGenes) * length(GRN@config$TF_peak_connectionTypes)
        
        pb <- progress::progress_bar$new(total = nElemsTotal)
        
        
        pageCounter = 1 
        for (TF_peak.fdr_cur in rev(TF_peak.fdrs)) {
            
            for (peak_gene.fdr_cur in peak_gene.fdrs) {
                
                for (allowMissingTFsCur in allowMissingTFs) {
                    
                    for (allowMissingGenesCur in allowMissingGenes) {
                        
                        for (TF_peak.connectionTypeCur in GRN@config$TF_peak_connectionTypes) {
                            
                            pb$tick()
                            
                            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                                
                                elemCur = "TF"
                                dataCur0.df = as.data.frame(stats_details.l[["0"]][[TF_peak.fdr_cur]][[peak_gene.fdr_cur]] [[allowMissingTFsCur]] [[allowMissingGenesCur]] [[TF_peak.connectionTypeCur]] [[elemCur]])
                                dataCur1.df = as.data.frame(stats_details.l[["1"]][[TF_peak.fdr_cur]][[peak_gene.fdr_cur]] [[allowMissingTFsCur]] [[allowMissingGenesCur]] [[TF_peak.connectionTypeCur]] [[elemCur]])
                                
                                if (nrow(dataCur0.df) == 0) {
                                    dataCur0.df = data.frame(. = "MISSING", Freq = 0)
                                }
                                if (nrow(dataCur1.df) == 0) {
                                    dataCur1.df = data.frame(. = "MISSING", Freq = 0)
                                }
                                
                                dataCur.df = rbind(dplyr::mutate(dataCur0.df, networkType = "real", connectionType = elemCur), 
                                                   dplyr::mutate(dataCur1.df, networkType = "background", connectionType = elemCur)) %>% 
                                    tibble::as_tibble()
                                
                                for (elemCur in c("gene","peak.gene","peak.TF")) {
                                    
                                    dataCur0.df = as.data.frame(stats_details.l[["0"]][[TF_peak.fdr_cur]][[peak_gene.fdr_cur]] [[allowMissingTFsCur]] [[allowMissingGenesCur]] [[TF_peak.connectionTypeCur]] [[elemCur]])
                                    dataCur1.df = as.data.frame(stats_details.l[["1"]][[TF_peak.fdr_cur]][[peak_gene.fdr_cur]] [[allowMissingTFsCur]] [[allowMissingGenesCur]] [[TF_peak.connectionTypeCur]] [[elemCur]])
                                    
                                    if (nrow(dataCur0.df) == 0) {
                                        dataCur0.df = data.frame(. = "MISSING", Freq = 0)
                                    }
                                    if (nrow(dataCur1.df) == 0) {
                                        dataCur1.df = data.frame(. = "MISSING", Freq = 0)
                                    }
                                    
                                    dataCur.df = rbind(dataCur.df,
                                                       dplyr::mutate(dataCur0.df, networkType = "real", connectionType = elemCur), 
                                                       dplyr::mutate(dataCur1.df, networkType = "background", connectionType = elemCur))
                                    
                                }
                                
                                dataCur.df$networkType = as.factor(dataCur.df$networkType)
                                
                                titlePlot =  paste0("TF_peak.fdr: ", TF_peak.fdr_cur, 
                                                    ", peak_gene.fdr: ", peak_gene.fdr_cur, 
                                                    "\nallowMissingTFs: ", allowMissingTFsCur, 
                                                    ", allowMissingGenes: ", allowMissingGenesCur, 
                                                    ",\n TF_peak.connectionType: ", TF_peak.connectionTypeCur)
                                
                                if (max(dataCur.df$Freq) > 0) {
                                    
                                    g = ggplot2::ggplot(dataCur.df, ggplot2::aes(.data$networkType, log10(.data$Freq), fill = .data$networkType)) + ggplot2::geom_boxplot() + ggplot2::theme_bw() + 
                                        ggplot2::xlab("Network type")  + ggplot2::ylab(paste0("log10(Number of connections per category",  ", empty = 0)")) +
                                        ggplot2::ggtitle(titlePlot) + 
                                        ggplot2::facet_wrap(~connectionType, scales = "free") + 
                                        ggplot2::scale_fill_manual("Network type", values = c("real" =  "red", "background" = "gray"), 
                                                                   labels = c("real" =  "real", "background" = "background"), drop = FALSE)
                                    
                                    suppressWarnings(plot(g))
                                    
                                } else {
                                    
                                    plot(c(0, 1), c(0, 1), ann = FALSE, bty = 'n', type = 'n', axes = FALSE, main = titlePlot)
                                    message = paste0(titlePlot, "\nNo data to show")
                                    text(x = 0.5, y = 0.5, message, cex = 1.2, col = "red")
                                }
                            }
                            pageCounter = pageCounter + 1  
                            
                            
                        } #end  for (TF_peak.connectionTypeCur in TF_peak.connectionTypesAll)
                        
                    } # end  for (allowMissingGenesCur in c(FALSE, TRUE))
                    
                } # end for (allowMissingTFsCur in c(FALSE, TRUE))
                
            } # end for each peak_gene.fdr_cur
            
        } # end for TF_peak.fdr_cur
        
        .checkPageNumberValidity(pages, pageCounter)
        if (!is.null(file)) {
            grDevices::dev.off()
        }
    }
    
    
    .printExecutionTime(start)
    
}

############ Plot correlations for any connection type ###############


#' Plot scatter plots of the underlying count data for either TF-peak, peak-gene or TF-gene pairs for a \code{\linkS4class{GRN}} object
#' 
#' The user can select multiple filters to plot only pairs of interest. The data that is shown is the same that has been used to construct the eGRN.
#' 
#' @template GRN 
#' @param type Character(1). Default \code{"all.filtered"}. Either \code{"TF-peak"}, \code{"peak-gene"}, \code{"TF-gene"}, or \code{"all.filtered"}. Default \code{"all.filtered"}. 
#' Which connections to plot? \code{"all.filtered"} plots all TF-peak and peak-gene pairs from the filtered eGRN as stored in the object.
#' @param TF.IDs Character(). Default \code{NULL}. Character vector of TF IDs to include. If set to \code{NULL}, this filter will be ignored. 
#' Only applicable when \code{type = "TF-peak"} or \code{type = "TF-gene"}. 
#' @param peak.IDs Character(). Default \code{NULL}. Character vector of peak IDs to include. If set to \code{NULL}, this filter will be ignored. 
#' Only applicable when \code{type = "TF-peak"} or \code{type = "peak-gene"}. 
#' @param gene.IDs Character(). Default \code{NULL}. Character vector of gene IDs (Ensembl) to include. If set to \code{NULL}, this filter will be ignored. 
#' Only applicable when \code{type = "TF-gene"} or \code{type = "peak-gene"}. 
#' @param nMax Numeric(1). Default 10. Filter for all types of pairs: maximum number of selected pairs that fulfill all other filters that should be plotted.
#' If set to 0, this filter will be disabled and all pairs that fulfill the user-defined criteria will be plotted. If set to a value > 0, different pairs may be selected each time the function is run
#'  (if the total number of remaining pairs is large enough)
#' @param nSelectionType \code{random} or \code{top}. Default \code{top}. Only applicable if \code{nMax} is set to a value > 0. 
#' If set to \code{top}, only the top features will be plotted while \code{random} plots randomly selected features.
#' @param min_abs_r Numeric[0,1]. Default 0. Filter for all types of pairs: Minimum correlation coefficient (absolute value) required to include a particular pair.
#' @param TF_peak_maxFDR Numeric[0,1]. Default 0.2. Filter for TF-peak pairs: Which maximum FDR should a pair to plot have? Only applicable when \code{type = "TF-peak"}. 
#' @param peak_gene_max_rawP Numeric[0,1]. Default 0.2. Filter for peak-gene pairs: Which maximum FDR should a pair to plot have? Only applicable when \code{type = "peak-gene"}. 
#' @param TF_gene_max_rawP Numeric[0,1]. Default 0.2. Filter for TF-gene pairs: Which maximum FDR should a pair to plot have? Only applicable when \code{type = "TF-gene"}. 
#' @template outputFolder
#' @template basenameOutput
#' @param dataType Character vector. One of, or both of, \code{real} or \code{background}. Default \code{real}. For which type, real or background data, to produce the diagnostic plots?
#' @param corMethod Character. Either \code{NULL} (the default) or one of \code{pearson}, \code{spearman} or \code{bicor}. 
#' Method for calculating the correlation coefficient. If set to \code{NULL}, the correlation method that has been used in the GraNIE object is used.
#' For testing nd visualizing data, this can be changed, though.
#' For \code{pearson} and \code{spearman} , see \link{cor} for details. \code{bicor} denotes the *biweight midcorrelation*, a correlation measure based on medians as
#' calculated by \code{WGCNA::bicorAndPvalue}. Both \code{spearman} and \code{bicor} are considered more robust measures that are less prone to be affected by outliers.

#' @template plotAsPDF
#' @param plotsPerPage Integer vector of length 2. Default \code{c(2,2)}. How man plots should be put on one page? The first number denotes the number of rows, the second one the number of columns.
#' @template pdf_width
#' @template pdf_height
#' @template forceRerun
#' @return An updated \code{\linkS4class{GRN}} object.
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plotCorrelations(GRN, nMax = 1, min_abs_r = 0.8, plotsPerPage = c(1,1), plotAsPDF = FALSE)
#' @export
plotCorrelations <- function(GRN, type = "all.filtered", 
                             TF.IDs = NULL, peak.IDs = NULL, gene.IDs = NULL, 
                             min_abs_r = 0, TF_peak_maxFDR = 0.2, peak_gene_max_rawP = 0.2, TF_gene_max_rawP = 0.2,
                             nMax = 10, nSelectionType = "random",
                             dataType = c("real"), corMethod = NULL,
                             outputFolder = NULL, 
                             basenameOutput = NULL, 
                             plotAsPDF = TRUE, plotsPerPage = c(2,2), pdf_width = 10, pdf_height = 8,
                             forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assertSubset(type, c("TF-peak", "peak-gene", "TF-gene", "all.filtered"), empty.ok = FALSE)
    
    checkmate::assert(checkmate::checkNull(TF.IDs), checkmate::checkCharacter(TF.IDs, min.chars = 1))
    checkmate::assert(checkmate::checkNull(peak.IDs), checkmate::checkCharacter(peak.IDs, min.chars = 1))
    checkmate::assert(checkmate::checkNull(gene.IDs), checkmate::checkCharacter(gene.IDs, min.chars = 1))
    
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    
    checkmate::assertSubset(dataType, c("real", "background"), empty.ok = FALSE)
    checkmate::assert(checkmate::checkNull(corMethod), checkmate::checkChoice(corMethod, c("pearson", "spearman", "bicor")))
    
    checkmate::assertNumber(nMax, lower = 0)
    checkmate::assertChoice(nSelectionType, c("random", "top"))
    
    checkmate::assertNumber(TF_peak_maxFDR, lower = 0, upper = 1)
    checkmate::assertNumber(peak_gene_max_rawP, lower = 0, upper = 1)
    checkmate::assertNumber(TF_gene_max_rawP, lower = 0, upper = 1)
    checkmate::assertNumber(min_abs_r, lower = 0, upper = 1)
    
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertIntegerish(plotsPerPage, lower = 1, len = 2)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    # checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    if (plotAsPDF) {
        file = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_correlations"), basenameOutput), ".pdf")
        futile.logger::flog.info(paste0("Plotting to file ", file))
    } else {
        file = NULL
    }
    
    permIndex = c()
    if ("real" %in% dataType) permIndex = c("0")
    if ("background" %in% dataType) permIndex = c(permIndex, "1")
    
    plotAllFiltered = FALSE
    
    futile.logger::flog.info(paste0("Plotting correlations for ", type))
    
    if ((!is.null(file) && !file.exists(file)) | is.null(file) | forceRerun) {
        
        plots.l = list()
        
        if (type == "all.filtered") {
            type = c("TF-peak", "peak-gene")
            plotAllFiltered = TRUE
        }
        
        if (!is.null(peak.IDs) && any(!peak.IDs %in% GRN@annotation$peaks$peak.ID)) {
            message = paste0("The peak.IDs argument contains at least one peak that could not be found in the object within all peaks as defined in GRN@annotation$peaks$peak.ID.")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
        }
        if (!is.null(gene.IDs) && any(!gene.IDs %in% GRN@annotation$genes$gene.ENSEMBL)) {
            message = paste0("The gene.IDs argument contains at least one gene that could not be found in the object within all genes as defined in GRN@annotation$genes$gene.ENSEMBL.")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
        }
        if (!is.null(TF.IDs) && any(!TF.IDs %in% GRN@annotation$TFs$TF.ID)) {
            message = paste0("The TF.IDs argument contains at least one TF that could not be found in the object within all TFs as defined in GRN@annotation$TFs$TF.ID.")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
        }
        
        if ("TF-peak" %in% type) {
            
            plots.l = .plotCorrelation_TF_peak(GRN, permIndex, plotAllFiltered, TF.IDs, peak.IDs, TF_peak_maxFDR, min_abs_r, nMax, nSelectionType, corMethod)
            
        } 
        
        if ( "peak-gene" %in% type) {
            
            plots.l = c(plots.l, 
                        .plotCorrelation_peak_gene(GRN, permIndex, plotAllFiltered, peak.IDs, gene.IDs, peak_gene_max_rawP, min_abs_r, nMax, nSelectionType, corMethod))
            
        } 
        
        if ("TF-gene" %in% type) {
            
            plots.l = c(plots.l, .plotCorrelation_TF_gene(GRN, permIndex, plotAllFiltered, TF.IDs, gene.IDs, TF_gene_max_rawP, min_abs_r, nMax, corMethod))
        } 
        if (length(plots.l) > 0) {
            futile.logger::flog.info(paste0(" Plotting", ifelse(!is.null(file), paste0(" to file ", file), " directly"), ". This may take a while."))
            .printMultipleGraphsPerPage(plots.l, nCol = plotsPerPage[2], nRow = plotsPerPage[1], pdfFile = file, height = pdf_height, width = pdf_width)
        }
        
        
    } else {
        futile.logger::flog.info(paste0("All output files already exist. Set forceRerun = TRUE to regenerate and overwrite."))
        
    }
    
    .printExecutionTime(start)
    
    GRN
}

.plotCorrelation_TF_peak <- function(GRN, permIndex, plotAllFiltered, TF.IDs, peak.IDs, TF_peak_maxFDR, min_abs_r, nMax, nSelectionType, corMethod, digits_round = 3) {
    
    filters = c()
    if (nMax > 0)       filters = c(filters, paste0("Subselection (max=", nMax, ")"))
    if (TF_peak_maxFDR < 1) filters = c(filters, paste0("FDR (max=", TF_peak_maxFDR, ")"))
    if (min_abs_r > 0)      filters = c(filters, paste0("Correlation coefficient (abs) (min=", min_abs_r, ")"))
    
    if (!is.null(TF.IDs) && !is.null(peak.IDs)) {
        filters = c(filters, paste0("Specific TF-peak pairs only: ", paste(TF.IDs, peak.IDs, sep = "+", collapse = ", ")))
    } else {
        if (!is.null(TF.IDs))   filters = c(filters, paste0("TF IDs only: ", paste0(unique(TF.IDs), collapse = ", ")))
        if (!is.null(peak.IDs)) filters = c(filters, paste0("peak IDs only: ", paste0(unique(peak.IDs), collapse = ", ")))
        
    }
    
    
    futile.logger::flog.info(paste0("User-defined filters:\n ", paste0(filters, collapse = "\n ")))
    
    if (is.null(corMethod)) {
        corMethod = GRN@config$parameters$corMethod_TF_Peak
    }
    
    plots.l = list()
    plotIndex = 1
    
    for (permIndexCur in permIndex) {
        
        if (plotAllFiltered) {
            
            if (is.null(GRN@connections$all.filtered[[permIndexCur]])) {
                message = paste0("Could not find the all.filtered slot even though type = \"all.filtered\". Change the type or rerun the filterGRNAndConnectGenes function.")
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
            }
            data.df = GRN@connections$all.filtered[[permIndexCur]]
            
        } else {
            data.df = GRN@connections$TF_peaks[[permIndexCur]]$main
        }
        
        if (!is.null(TF.IDs) && !is.null(peak.IDs)) { 
            
            if (length(TF.IDs) != length(peak.IDs)) {
                message = paste0("If both TF and peak IDs are given, both must have the same length.")
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
            }
            
            IDs_keep = paste0(TF.IDs, "_", peak.IDs)
            data.df = data.df %>% 
                dplyr::mutate(IDs_combined = paste0(.data$TF.ID, "_", .data$peak.ID)) %>% 
                dplyr::filter(.data$IDs_combined %in% IDs_keep) 
            
            # Enforce the same order
            data.df = dplyr::slice(data.df, match(IDs_keep, .data$IDs_combined))
            
            
        } else {
            
            if (!is.null(TF.IDs)) {
                data.df = dplyr::filter(data.df, .data$TF.ID %in% TF.IDs)
            }
            
            if (!is.null(peak.IDs)) {
                data.df = dplyr::filter(data.df, .data$peak.ID %in% peak.IDs)
            }
        }
        
        
        
        
        data.df = data.df %>%
            dplyr::filter(.data$TF_peak.fdr <= TF_peak_maxFDR,
                          abs(.data$TF_peak.r) >= min_abs_r)
        
        data.df = .maxFilter(data.df, nMax, nSelectionType)
        
        
        if (.checkForZeroPlots(data.df) ) {
            return(list())
        }
        
        TF.IDs2   = dplyr::pull(data.df, "TF.ID") %>% as.character()
        peak.IDs2 = dplyr::pull(data.df, "peak.ID") %>% as.character()
        
        futile.logger::flog.info(paste0(" Based on user-provided data and filters, ", nrow(data.df), " pairs remain and will be plotted."))
        
        # Get the counts. getcounts should be used due to the permuted versiohn
        countsPeaks = getCounts(GRN, type = "peaks", permuted = FALSE, asMatrix = TRUE, includeFiltered = TRUE)
        countsRNA   = getCounts(GRN, type = "rna", permuted = ifelse(permIndexCur == "0", FALSE, TRUE), asMatrix = TRUE, includeFiltered = TRUE)
        #countsRNA2   = getCounts(GRN, type = "rna", permuted = FALSE, asMatrix = TRUE, includeFiltered = TRUE)
        # TODO: Enable also for background, but why r is different from what is stored in the object?
        
        for (i in seq_len(nrow(data.df))) {
            
            futile.logger::flog.info(paste0("  Preparing plot for TF ", .printTF(GRN, TF.IDs2[i]) , " and peak ", peak.IDs2[i]))
            
            TF.ENSEMBL = GRN@annotation$TFs %>% dplyr::filter(.data$TF.ID %in% TF.IDs2[i]) %>% dplyr::pull(TF.ENSEMBL)
            stopifnot(length(TF.ENSEMBL) == 1) 
            # rowIndexTF = which(rownames(GRN@data$RNA$counts) == TF.ENSEMBL)
            # stopifnot(length(rowIndexTF) == 1) 
            # rowIndexPeak = which(rownames(GRN@data$peaks$counts) == peak.IDs2[i])
            # stopifnot(length(rowIndexPeak) == 1) 
            # 
            # 
            # values.df = tibble::tibble(x = GRN@data$RNA$counts[rowIndexTF, ], 
            #                            y = GRN@data$peaks$counts[rowIndexPeak,])
            # 
            # Gives different values due to column selection
            values.df = tibble::tibble(x = countsRNA[TF.ENSEMBL, GRN@config$sharedSamples] %>% as.vector(), 
                                       y = countsPeaks[peak.IDs2[i], GRN@config$sharedSamples] %>% as.vector())
            
            cor_cur = .correlateData(dplyr::pull(values.df, "x"), dplyr::pull(values.df, "y"), corMethod)
            
            
            # Check whether we have this pair in the TF-peak and all.filtered connections
            con.subset.df = GRN@connections$TF_peaks[[permIndexCur]]$main %>%
                dplyr::filter(.data$TF.ID == TF.IDs2[i], .data$peak.ID == peak.IDs2[i])
            
            stopifnot(nrow(con.subset.df) == 1)
            
            # Verify identify of values
            if ((is.null(corMethod) | GRN@config$parameters$corMethod_TF_Peak == corMethod) & nrow(con.subset.df) == 1) {
                corCalc = dplyr::pull(con.subset.df, "TF_peak.r")
                stopifnot(abs(cor_cur - corCalc) < 0.01)
            }
            
            pair_in_TF_peak_links = FALSE
            FDRcur = paste0("NA (>", round(max(.getFDR_TF_peak_vector(GRN)), digits_round), ")")
            if (nrow(con.subset.df) == 1) {
                FDRcur = dplyr::pull(con.subset.df, "TF_peak.fdr") %>% round(digits_round)
                pair_in_TF_peak_links = TRUE
            }
            
            nRows_filtered = 0
            if (!is.null(GRN@connections$all.filtered[[permIndexCur]])) {
                nRows_filtered = GRN@connections$all.filtered[[permIndexCur]] %>%
                    dplyr::filter(.data$TF.ID == TF.IDs2[i], .data$peak.ID == peak.IDs2[i]) %>%
                    nrow()
            }
            
            
            ## PLOT
            dataTypeStr = ifelse(permIndexCur > 0, "permuted connections", "real connections")
            
            titleCur = paste0("TF=", .printTF(GRN, TF.IDs2[i]), ", peak=", peak.IDs2[i], 
                              ":\nCor = ", round(cor_cur, digits_round), " (", corMethod, "), stored FDR = ", FDRcur, 
                              "\n", dataTypeStr, ", # eGRN links: ", nRows_filtered)
            
            plots.l[[plotIndex]]  = .produceCorrelationPlot(values.df, titleCur, 
                                                            label_x = "(Normalized) TF expression",
                                                            label_y = "(Normalized) peak accessibility")
            
            plotIndex = plotIndex  + 1
            
        }
        
        
    } # end for (permIndexCur in permIndex)
    
    plots.l
}


.plotCorrelation_peak_gene <- function(GRN, permIndex, plotAllFiltered, peak.IDs, gene.IDs, p_raw, min_abs_r, nMax, nSelectionType, corMethod, digits_round = 3) {
    
    
    filters = c()
    if (nMax > 0) filters = c(filters, paste0("Subselection  (max=", nMax, ")"))
    if (p_raw < 1) filters = c(filters, paste0("Raw p-value (max=", p_raw, ")"))
    if (min_abs_r > 0) filters = c(filters, paste0("Correlation coefficient (abs) (min=", min_abs_r, ")"))
    
    if (!is.null(peak.IDs) && !is.null(gene.IDs)) {
        filters = c(filters, paste0("Specific peak-gene pairs only: ", paste(peak.IDs, gene.IDs, sep = "+", collapse = ", ")))
    } else {
        if (!is.null(peak.IDs)) filters = c(filters, paste0("peak IDs only: ", paste0(unique(peak.IDs), collapse = ", ")))
        if (!is.null(gene.IDs)) filters = c(filters, paste0("Gene IDs only: ", paste0(unique(gene.IDs), collapse = ", ")))
        
    }
    
    
    
    
    futile.logger::flog.info(paste0("User-defined filters:\n ", paste0(filters, collapse = "\n ")))
    
    if (is.null(corMethod)) {
        corMethod = GRN@config$parameters$corMethod_peak_gene
    }
    
    plots.l = list()
    plotIndex = 1
    
    for (permIndexCur in permIndex) {
        
        if (plotAllFiltered) {
            
            if (is.null(GRN@connections$all.filtered[[permIndexCur]])) {
                message = paste0("Could not find the all.filtered slot even though type = \"all.filtered\". Change the type or rerun the filterGRNAndConnectGenes function.")
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
            }
            data.df = GRN@connections$all.filtered[[permIndexCur]]
        } else {
            data.df = GRN@connections$peak_genes[[permIndexCur]]
        }
        
        if (!is.null(peak.IDs) && !is.null(gene.IDs)) { 
            
            if (length(gene.IDs) != length(peak.IDs)) {
                message = paste0("If both peak and gene IDs are given, both must have the same length.")
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
            }
            
            IDs_keep = paste0(peak.IDs, "_", gene.IDs)
            data.df = data.df %>% 
                dplyr::mutate(IDs_combined = paste0(.data$peak.ID, "_", .data$gene.ENSEMBL)) %>% 
                dplyr::filter(.data$IDs_combined %in% IDs_keep) 
            
            # Enforce the same order
            data.df = dplyr::slice(data.df, match(IDs_keep, .data$IDs_combined))
            
            
        } else {
            
            if (!is.null(peak.IDs)) {
                data.df = dplyr::filter(data.df, .data$peak.ID %in% peak.IDs)
            }
            
            if (!is.null(gene.IDs)) {
                data.df = dplyr::filter(data.df, .data$gene.ENSEMBL %in% gene.IDs)
            }
        }
        
        
        
        
        data.df = data.df %>%
            dplyr::filter(.data$peak_gene.p_raw <= p_raw,
                          abs(.data$peak_gene.r) >= min_abs_r)
        
        data.df = .maxFilter(data.df, nMax, nSelectionType)
        
        
        if (.checkForZeroPlots(data.df) ) {
            return(list())
        }
        
        peak.IDs2 = dplyr::pull(data.df, "peak.ID") %>% as.character()
        gene.IDs2   = dplyr::pull(data.df, "gene.ENSEMBL") %>% as.character()
        
        futile.logger::flog.info(paste0(" Based on user-provided data and filters, ", nrow(data.df), " pairs remain and will be plotted."))
        
        countsPeaks = getCounts(GRN, type = "peaks", permuted = FALSE, asMatrix = TRUE, includeFiltered = TRUE)
        countsRNA   = getCounts(GRN, type = "rna", permuted = ifelse(permIndexCur == "0", FALSE, TRUE), asMatrix = TRUE, includeFiltered = TRUE)
        
        
        for (i in seq_len(nrow(data.df))) {
            
            futile.logger::flog.info(paste0("  Preparing plot for peak ",  peak.IDs2[i], " and gene ", .printGene(GRN, gene.IDs2[i])))
            
            # rowIndexPeak = which(rownames(GRN@data$peaks$counts) == peak.IDs2[i])
            # stopifnot(length(rowIndexPeak) == 1) 
            # rowIndexGene = which(rownames(GRN@data$RNA$counts) == gene.IDs2[i])
            # stopifnot(length(rowIndexPeak) == 1) 
            # 
            # values.df = tibble::tibble(x = GRN@data$RNA$counts[rowIndexGene, ], 
            #                            y = GRN@data$peaks$counts[rowIndexPeak,])
            values.df = tibble::tibble(y = countsRNA[gene.IDs2[i], GRN@config$sharedSamples] %>% as.vector(), 
                                       x = countsPeaks[peak.IDs2[i], GRN@config$sharedSamples] %>% as.vector())
            
            cor_cur = .correlateData(dplyr::pull(values.df, "x"), dplyr::pull(values.df, "y"), corMethod)
            
            
            # Check whether we have this pair in the peak-gene and all.filtered connections
            con.subset.df = GRN@connections$peak_genes[[permIndexCur]] %>%
                dplyr::filter(.data$peak.ID == peak.IDs2[i], .data$gene.ENSEMBL == gene.IDs2[i],)
            
            stopifnot(nrow(con.subset.df) == 1)
            
            # Verify identify of values
            if ((is.null(corMethod) | GRN@config$parameters$corMethod_peak_gene == corMethod) & nrow(con.subset.df) == 1) {
                corCalc = dplyr::pull(con.subset.df, "peak_gene.r")
                stopifnot(abs(cor_cur - corCalc) < 0.01)
            }
            
            pair_in_peak_gene_links = FALSE
            praw_cur = paste0("NA (not tested)")
            if (nrow(con.subset.df) == 1) {
                praw_cur = dplyr::pull(con.subset.df, "peak_gene.p_raw") %>% round(digits_round)
                pair_in_peak_gene_links = TRUE
            }
            
            nRows_filtered = 0
            if (!is.null(GRN@connections$all.filtered[[permIndexCur]])) {
                nRows_filtered = GRN@connections$all.filtered[[permIndexCur]] %>%
                    dplyr::filter(.data$peak.ID == peak.IDs2[i], .data$gene.ENSEMBL == gene.IDs2[i], )  %>%
                    nrow()
            }
            
            
            ## PLOT
            dataTypeStr = ifelse(permIndexCur > 0, "permuted connections", "real connections")
            
            titleCur = paste0("Peak=", peak.IDs2[i], ", gene=", .printGene(GRN, gene.IDs2[i]),  
                              ":\nCor = ", round(cor_cur, digits_round), " (", corMethod, "), raw p = ", praw_cur, 
                              "\n", dataTypeStr, ", # eGRN links: ", nRows_filtered)
            
            plots.l[[plotIndex]]  = .produceCorrelationPlot(values.df, titleCur, 
                                                            label_x = "(Normalized) gene expression",
                                                            label_y = "(Normalized) peak accessibility")
            
            
            plotIndex = plotIndex  + 1
            
        }
        
        
    } # end for (permIndexCur in permIndex)
    
    plots.l
}


.plotCorrelation_TF_gene <- function(GRN, permIndex, plotAllFiltered, TF.IDs, gene.IDs, p_raw, min_abs_r, nMax, nSelectionType, corMethod, digits_round = 3) {
    
    filters = c()
    if (nMax > 0) filters = c(filters, paste0("Subselection (max=", nMax, ")"))
    if (p_raw < 1) filters = c(filters, paste0("Raw p-value (max=", p_raw, ")"))
    if (min_abs_r > 0) filters = c(filters, paste0("Correlation coefficient (abs) (min=", min_abs_r, ")"))
    
    if (!is.null(TF.IDs) && !is.null(gene.IDs)) {
        filters = c(filters, paste0("Specific TF-gene pairs only: ", paste(TF.IDs, gene.IDs, sep = "+", collapse = ", ")))
    } else {
        if (!is.null(TF.IDs)) filters = c(filters, paste0("TF IDs only: ", paste0(unique(TF.IDs), collapse = ", ")))
        if (!is.null(gene.IDs)) filters = c(filters, paste0("Gene IDs only: ", paste0(unique(gene.IDs), collapse = ", ")))
        
    }
    
    
    
    
    futile.logger::flog.info(paste0("User-defined filters:\n ", paste0(filters, collapse = "\n ")))
    
    if (is.null(corMethod)) {
        if (is.null(GRN@config$parameters$corMethod_TF_gene)) { # previous GRN versions didnt have it yet
            corMethod = GRN@config$parameters$corMethod_TF_Peak
        } else {
            corMethod = GRN@config$parameters$corMethod_TF_gene
        }
        
    }
    
    plots.l = list()
    plotIndex = 1
    
    for (permIndexCur in permIndex) {
        
        if (is.null(GRN@connections$TF_genes.filtered[[permIndexCur]])) {
            message = paste0("Could not find the TF_genes.filtered slot even though type = \"all.filtered\". Change the type or rerun the add_TF_gene_correlation function.")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
        }
        data.df = GRN@connections$TF_genes.filtered[[permIndexCur]]
        
        if (!is.null(TF.IDs) && !is.null(gene.IDs)) { 
            
            if (length(gene.IDs) != length(TF.IDs)) {
                message = paste0("If both TF and gene IDs are given, both must have the same length.")
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
            }
            
            IDs_keep = paste0(TF.IDs, "_", gene.IDs)
            data.df = data.df %>% 
                dplyr::mutate(IDs_combined = paste0(.data$TF.ID, "_", .data$gene.ENSEMBL)) %>% 
                dplyr::filter(.data$IDs_combined %in% IDs_keep) 
            
            # Enforce the same order
            data.df = dplyr::slice(data.df, match(IDs_keep, .data$IDs_combined))
            
            
        } else {
            
            if (!is.null(TF.IDs)) {
                data.df = dplyr::filter(data.df, .data$TF.ID %in% TF.IDs)
            }
            
            if (!is.null(gene.IDs)) {
                data.df = dplyr::filter(data.df, .data$gene.ENSEMBL %in% gene.IDs)
            }
        }
        
        
        data.df = data.df %>%
            dplyr::filter(.data$TF_gene.p_raw <= p_raw,
                          abs(.data$TF_gene.r) >= min_abs_r)
        
        data.df = .maxFilter(data.df, nMax, nSelectionType)
        
        if (.checkForZeroPlots(data.df) ) {
            return(list())
        }
        
        TF.IDs2   = dplyr::pull(data.df, "TF.ID") %>% as.character()
        gene.IDs2 = dplyr::pull(data.df, "gene.ENSEMBL") %>% as.character()
        
        futile.logger::flog.info(paste0(" Based on user-provided data and filters, ", nrow(data.df), " pairs remain and will be plotted."))
        
        countsRNA   = getCounts(GRN, type = "rna", permuted = ifelse(permIndexCur == "0", FALSE, TRUE), asMatrix = TRUE, includeFiltered = TRUE)
        
        for (i in seq_len(nrow(data.df))) {
            
            futile.logger::flog.info(paste0("  Preparing plot for TF ", .printTF(GRN, TF.IDs2[i]), " and gene ", .printGene(GRN, gene.IDs2[i])))
            
            TF.ENSEMBL = GRN@annotation$TFs %>% dplyr::filter(.data$TF.ID %in% TF.IDs2[i]) %>% dplyr::pull(TF.ENSEMBL)
            stopifnot(length(TF.ENSEMBL) == 1) 
            # rowIndexTF = which(rownames(GRN@data$RNA$counts) == TF.ENSEMBL)
            # stopifnot(length(rowIndexTF) == 1) 
            # rowIndexGene = which(rownames(GRN@data$RNA$counts) == gene.IDs2[i])
            # stopifnot(length(rowIndexGene) == 1) 
            
            values.df = tibble::tibble(y = countsRNA[TF.ENSEMBL, GRN@config$sharedSamples] %>% as.vector(), 
                                       x = countsRNA[gene.IDs2[i], GRN@config$sharedSamples] %>% as.vector())
            
            # values.df = tibble::tibble(x = GRN@data$RNA$counts[rowIndexTF, ], 
            #                            y = GRN@data$RNA$counts[rowIndexGene,])
            # 
            # 
            
            cor_cur = .correlateData(dplyr::pull(values.df, "x"), dplyr::pull(values.df, "y"), corMethod)
            
            
            # Check whether we have this pair in the TF-gene filtered connections
            con.subset.df = GRN@connections$TF_genes.filtered[[permIndexCur]] %>%
                dplyr::filter(.data$TF.ID == TF.IDs2[i], .data$gene.ENSEMBL == gene.IDs2[i])
            
            stopifnot(nrow(con.subset.df) == 1)
            
            rawp = dplyr::pull(con.subset.df, "TF_gene.p_raw")
            
            # Verify identify of values
            if (!is.null(GRN@config$parameters$corMethod_TF_gene) && ((is.null(corMethod) | GRN@config$parameters$corMethod_TF_gene == corMethod) & nrow(con.subset.df) == 1)) {
                corCalc = dplyr::pull(con.subset.df, "TF_gene.r")
                stopifnot(abs(cor_cur - corCalc) < 0.01)
            }
            
            
            nRows_filtered = 0
            if (!is.null(GRN@connections$TF_genes.filtered[[permIndexCur]])) {
                nRows_filtered = GRN@connections$TF_genes.filtered[[permIndexCur]] %>%
                    dplyr::filter(.data$TF.ID == TF.IDs2[i], .data$gene.ENSEMBL == gene.IDs2[i]) %>%
                    nrow()
            }
            
            
            ## PLOT
            dataTypeStr = ifelse(permIndexCur > 0, "permuted connections", "real connections")
            
            titleCur = paste0("TF=", .printTF(GRN, TF.IDs2[i]), ", gene=", .printGene(GRN, gene.IDs2[i]), 
                              ":\nCor = ", round(cor_cur, digits_round), " (", corMethod, "), raw p-value = ", round(rawp, digits_round),
                              "\n", dataTypeStr, ", # eGRN links: ", nRows_filtered)
            
            plots.l[[plotIndex]]  = .produceCorrelationPlot(values.df, titleCur, 
                                                            label_x = "(Normalized) TF expression",
                                                            label_y = "(Normalized) gene expression")
            
            plotIndex = plotIndex  + 1
            
        }
        
        
    } # end for (permIndexCur in permIndex)
    
    plots.l
}

.maxFilter <- function(data.df, nMax, nSelectionType) {
    
    if (nMax > 0) {
        
        if (nSelectionType == "random") {
            data.df = dplyr::slice_sample(data.df, n = nMax)
        } else {
            data.df = dplyr::slice_head(data.df, n = nMax)
        }
        
    }
    
    data.df
}



.produceCorrelationPlot <- function(values, title, label_x, label_y) {
    
    x_min = min(-0.01, min(values$x, na.rm = TRUE))
    y_min = min(-0.01, min(values$y, na.rm = TRUE))
    
    # Test for NA, should not happen
    na_elems = which(is.na(values))
    if (length(na_elems) > 0) {
        message = paste0("NA values in correlation data")
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    }
    
    g = ggplot(values, aes(.data$x, .data$y)) + 
        geom_smooth(method = "lm", formula = "y ~ x", col = "black", na.rm = TRUE) + 
        geom_point(size = rel(0.5)) +
        xlim(x_min, NA) + ylim(y_min, NA) + 
        xlab(label_x) + ylab(label_y) + 
        ggtitle(title) + 
        theme_bw() +
        theme(plot.title = element_text(size = rel(0.9)))
    
    g
}


.checkForZeroPlots <- function(data.df) {
    if (nrow(data.df) == 0) {
        message = paste0("plotCorrelations: No connections (left) after filtering. No plots are produced. Rerun the function and change the filters.")
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
        return(TRUE)
    } else {
        return(FALSE)
    }
}





######## General & communities Stats Functions ########


#' Plot general structure and connectivity statistics for a filtered \code{\linkS4class{GRN}} object
#' 
#' This function generates graphical summaries about the structure and connectivity of the TF-peak-gene and TF-gene graphs. These include, distribution of vertex types (TF, peak, gene) and edge types (tf-peak, peak-gene), the distribution of vertex degrees, and the most "important" vertices according to degree centrality and eigenvector centrality scores.
#' @template GRN
#' @template outputFolder
#' @template basenameOutput
#' @template forceRerun
#' @template plotAsPDF
#' @template pdf_width
#' @template pdf_height
#' @template pages
#' @return The same \code{\linkS4class{GRN}} object, without modifications. 
#' @seealso \code{\link{plotGeneralEnrichment}}
#' @seealso \code{\link{plotCommunitiesStats}}
#' @seealso \code{\link{plotCommunitiesEnrichment}}
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plotGeneralGraphStats(GRN, plotAsPDF = FALSE, pages = 1)
#' @export
plotGeneralGraphStats <- function(GRN, outputFolder = NULL, basenameOutput = NULL, 
                                  plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12, pages = NULL,
                                  forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    GRN = .addFunctionLogToObject(GRN)
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    fileCur = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_generalNetworkStats"), basenameOutput), ".pdf")
    if (!file.exists(fileCur) | forceRerun | !plotAsPDF) {
        
        if (plotAsPDF) {
            .checkOutputFile(fileCur)
            grDevices::pdf(fileCur, width = pdf_width, height = pdf_height)
            futile.logger::flog.info(paste0("Plotting to file ", fileCur))
        } else {
            futile.logger::flog.info(paste0("Plotting directly"))   
        }
        
        .checkGraphExistance(GRN)
        
        TF_peak_gene.df = GRN@graph$TF_peak_gene$table
        TF_gene.df = GRN@graph$TF_gene$table
        
        # pie charts
        totalVerteces = data.frame(Class = c("TF", "Peak", "Gene"),
                                   Count = c(dplyr::n_distinct(stats::na.omit(GRN@connections$all.filtered$`0`$TF.ID)),
                                             dplyr::n_distinct(stats::na.omit(GRN@connections$all.filtered$`0`$peak.ID)),
                                             dplyr::n_distinct(stats::na.omit(GRN@connections$all.filtered$`0`$gene.ENSEMBL)) 
                                   )
        )
        
        theme_plots = ggplot2::theme(axis.ticks = ggplot2::element_blank(), axis.title = ggplot2::element_blank(), axis.text = ggplot2::element_blank(), 
                                     legend.title = ggplot2::element_blank(), panel.background = ggplot2::element_rect(fill = "white"),
                                     plot.title = ggplot2::element_text(hjust = 0.5))
        
        
        pageCounter = 1
        
        # Page 1: Pie charts of the connections
        if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
            
            gVertexDist = ggplot2::ggplot(totalVerteces, ggplot2::aes(x = "", y = .data$Count, fill = .data$Class)) + ggplot2::geom_bar(stat = "identity") +
                ggplot2::coord_polar("y", start = 0) +
                ggplot2::scale_fill_manual(values = c("#3B9AB2", "#F21A00", "#E1AF00")) +
                ggplot2::geom_text(ggplot2::aes(label = paste0(round(.data$Count/sum(.data$Count) * 100, digits = 1), "%")),
                                   position = ggplot2::position_stack(vjust = 0.5)) +
                theme_plots +
                ggplot2::ggtitle(paste0("Vertices (n=", sum(totalVerteces$Count), ")"))
            
            geneDist = as.data.frame(table(droplevels(GRN@connections$all.filtered$`0`$gene.type)))
            if (nrow(geneDist) == 0) {
                gGeneDist = 
                    ggplot2::ggplot() + 
                    ggplot2::annotate("text", x = 4, y = 25, size = 5, color = "red", label = "Nothing to plot:\nNo genes found") + 
                    ggplot2::theme_void()
                
                
                message = "plotGeneralGraphStats: No genes found in the GRN object. Make sure the filtered connections contain also genes."
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
                
            } else {
                gGeneDist = ggplot2::ggplot(geneDist, ggplot2::aes(x =  "", y = .data$Freq, fill = .data$Var1)) + ggplot2::geom_bar(stat = "identity") +
                    ggplot2::coord_polar("y") +
                    ggplot2::geom_text(ggplot2::aes(label = paste0(round(.data$Freq/sum(.data$Freq) * 100, digits = 1 ), "%")),
                                       position = ggplot2::position_stack(vjust = 0.5)) +
                    theme_plots +
                    ggplot2::ggtitle(paste0("Genes (n=", sum(geneDist$Freq), ")"))
            }
            
            
            totalEdges = as.data.frame(table(TF_peak_gene.df$connectionType))
            if (nrow(totalEdges) == 0) {
                gEdgeDist = ggplot2::ggplot() + 
                    ggplot2::annotate("text", x = 4, y = 25, size = 5, color = "red", label = "Nothing to plot:\nNo genes found") + 
                    ggplot2::theme_void()
            } else {
                gEdgeDist = ggplot2::ggplot(totalEdges, ggplot2::aes(x = "", y = .data$Freq, fill = .data$Var1)) + ggplot2::geom_bar(stat = "identity") +
                    ggplot2::coord_polar("y", start = 0) +
                    ggplot2::scale_fill_manual(values = c("#BDC367", "#6BB1C1")) +
                    ggplot2::geom_text(ggplot2::aes(label = paste0(round(.data$Freq/sum(.data$Freq) * 100, digits = 1 ), "%")),
                                       position = ggplot2::position_stack(vjust = 0.5)) +
                    theme_plots +
                    ggplot2::ggtitle(paste0("Edges (n=", sum(totalEdges$Freq), ")"))
            }
            
            print((gVertexDist + gEdgeDist)/gGeneDist + patchwork::plot_layout(widths = c(2,1), guides = "collect"))
        }
        
        pageCounter = pageCounter + 1
        
        # First, we focus on the TF-peak-gene graph
        # Get degree stats and central vertexes in the TF-peak-gene graph
        TF_peak_gene.degree.stats = .getDegreeStats(GRN, TF_peak_gene.df)
        suffix = paste0(" in the filtered TF-peak-gene eGRN")
        if (nrow(TF_peak_gene.df) > 0) {
            #degreeDist.tbl = TF_peak_gene.degree.stats$tbl$degrees
            
            
            
            # Pages 2 to 4: TF-peak-gene network: Degree distribution, and top genes for 2 different measures
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                
                gDegrees  = TF_peak_gene.degree.stats$figures$degreeDist + ggplot2::ggtitle(paste0("Distribution of vertex degrees", suffix))
                print(gDegrees)
            } 
            pageCounter = pageCounter + 1
            
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                
                gTopGenes = TF_peak_gene.degree.stats$figures$topGenes   + ggplot2::ggtitle(paste0("Top degree-central genes", suffix))
                gTopTFs   = TF_peak_gene.degree.stats$figures$topTFs     + ggplot2::ggtitle(paste0("Top degree-central TFs", suffix))
                print(gTopGenes/gTopTFs)
            } 
            pageCounter = pageCounter + 1
            
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                
                eigen_stats = .getEigenCentralVertices(GRN, "TF_peak_gene")
                gTopEigenGenes = eigen_stats$topGenes + ggplot2::ggtitle(paste0("Top eigenvector-central genes", suffix))
                gTopEigenTFs   = eigen_stats$topTFs   + ggplot2::ggtitle(paste0("Top eigenvector-central TFs", suffix))
                print(gTopEigenGenes/gTopEigenTFs)
            } 
            pageCounter = pageCounter + 1
            
            ##
            # Now we focus on the TF-gene graph only, not the TF-peak-gene one
            ###
            suffix = paste0(" in the filtered TF-gene eGRN")
            # Get degree stats and central vertexes in the filtered GRN
            TF_gene.degree.stats = .getDegreeStats(GRN, TF_gene.df)
            
            
            # Pages 5 to 7: TF-gene network: Degree distribution, and top genes for 2 different measures
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                degreeDist.tbl = TF_gene.degree.stats$tbl$degrees
                gDegrees = TF_gene.degree.stats$figures$degreeDist + ggplot2::ggtitle(paste0("Distribution of vertex degrees", suffix))
                print(gDegrees)
            } 
            pageCounter = pageCounter + 1
            
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                
                gTopGenes = TF_gene.degree.stats$figures$topGenes + ggplot2::ggtitle(paste0("Top degree-central genes", suffix))
                gTopTFs = TF_gene.degree.stats$figures$topTFs + ggplot2::ggtitle(paste0("Top degree-central TFs", suffix))
                print(gTopGenes/gTopTFs)
            } 
            pageCounter = pageCounter + 1
            
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                
                # get top eigenvalue central TFs and genes
                eigen_stats = .getEigenCentralVertices(GRN, "TF_gene")
                gTopEigenGenes = eigen_stats$topGenes + ggplot2::ggtitle(paste0("Top eigenvector-central genes", suffix))
                gTopEigenTFs = eigen_stats$topTFs + ggplot2::ggtitle(paste0("Top eigenvector-central TFs", suffix))
                print(gTopEigenGenes/gTopEigenTFs)
                
            } 
            
        }
        
        .checkPageNumberValidity(pages, pageCounter)
        
        if (plotAsPDF) {grDevices::dev.off()}
        
    } else {
        .printDataAlreadyExistsMessage()
    }
    
    .printExecutionTime(start)
    
    GRN
    
}

.checkPageNumberValidity <- function(pages, pageCounter) {
    
    if (any(pages > pageCounter)) {
        message = paste0("At least one page could not be plotted because the total number of plots is only ", pageCounter, " while a larger page number has been requested. To fix this, re-adjust the page number(s) and execute the function again.")
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    }
}


#' Plot the general enrichement results
#' 
#' This function plots the results of the general enrichment analysis for every specified ontology.
#' 
#' @template GRN
#' @template outputFolder
#' @template basenameOutput
#' @template plotAsPDF
#' @template pdf_width
#' @template pdf_height
#' @template pages
#' @template forceRerun
#' @param ontology Character. \code{NULL} or vector of ontology names. Default \code{NULL}. Vector of ontologies to plot. The results must have been previously calculated otherwise an error is thrown.
#' @param p Numeric. Default 0.05. p-value threshold to determine significance.
#' @param topn_pvalue Numeric. Default 30. Maximum number of ontology terms that meet the p-value significance threshold to display in the enrichment dot plot
#' @param display_pAdj \code{TRUE} or \code{FALSE}. Default \code{FALSE}. Is the p-value being displayed in the plots the adjusted p-value? This parameter is relevant for KEGG, Disease Ontology, and Reactome enrichments, and does not affect GO enrichments.
#' @template maxWidth_nchar_plot
#' @return The same \code{\linkS4class{GRN}} object, without modifications.
#' @seealso \code{\link{plotCommunitiesEnrichment}}
#' @seealso \code{\link{plotTFEnrichment}}
#' @seealso \code{\link{calculateGeneralEnrichment}}
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plotGeneralEnrichment(GRN, plotAsPDF = FALSE, pages = 1)
#' @export
plotGeneralEnrichment <- function(GRN, outputFolder = NULL, basenameOutput = NULL, 
                                  ontology = NULL, topn_pvalue = 30, p = 0.05, 
                                  display_pAdj = FALSE, 
                                  maxWidth_nchar_plot = 50,
                                  plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12, pages = NULL,
                                  forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    
    
    GRN = .makeObjectCompatible(GRN)
    
    
    ontologiesFound = names(GRN@stats$Enrichment$general)
    
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    
    checkmate::assert(checkmate::checkNull(ontology), checkmate::checkSubset(ontology, ontologiesFound))
    checkmate::assertNumeric(p, lower = 0, upper = 1)
    checkmate::assertIntegerish(topn_pvalue, lower = 1)
    checkmate::assertFlag(display_pAdj)
    checkmate::assertIntegerish(maxWidth_nchar_plot, lower = 10)
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    
    .checkGraphExistance(GRN)
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    if (length(ontologiesFound) == 0) {
        message = "No ontologies found in GRN object for general enrichment. Run the function calculateGeneralEnrichment first"
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    }
    
    fileCur = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_generalEnrichment"), basenameOutput), ".pdf")
    if (!file.exists(fileCur) | forceRerun | !plotAsPDF) {
        
        GRN = .addFunctionLogToObject(GRN)
        
        if (plotAsPDF) {
            .checkOutputFile(fileCur)
            grDevices::pdf(fileCur, width = pdf_width, height = pdf_height)
            futile.logger::flog.info(paste0("Plotting to file ", fileCur))
        } else {
            futile.logger::flog.info(paste0("Plotting directly"))
        }
        
        ontologies =  ontologiesFound
        if (!is.null(ontology)) {
            ontologies = ontology
        } 
        
        futile.logger::flog.info(paste0("Found the following ontology results for plotting: ", paste0(ontologiesFound, collapse = ",")))
        futile.logger::flog.info(paste0("Plotting for the following user-selected ontologies: ", paste0(ontologies, collapse = ",")))
        
        pageCounter = 0
        
        for (ontologyCur in ontologies) {
            
            pageCounter = pageCounter + 1
            
            futile.logger::flog.info(paste0(" Ontology ", ontologyCur))   
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                .plotEnrichmentGeneral(data = GRN@stats$Enrichment$general[[ontologyCur]], 
                                       type = ontologyCur, 
                                       prefixTitle = "General Enrichment Analysis",
                                       topn_pvalue, p = p, maxWidth_nchar_plot = maxWidth_nchar_plot, display_pAdj = display_pAdj)
            }
            
        }
        
        .checkPageNumberValidity(pages, pageCounter)
        if (plotAsPDF) grDevices::dev.off()
        
    } else {
        .printDataAlreadyExistsMessage()
    }
    
    .printExecutionTime(start)
    
    GRN
}


.plotEnrichmentGeneral <- function(data, type, prefixTitle, topn_pvalue = 30, maxWidth_nchar_plot = 50, p = 0.05, display_pAdj = FALSE) {
    
    dataCur   = data[["results"]]
    paramsCur = data[["parameters"]]
    
    parameterStr = paste(names(paramsCur), paramsCur, sep = ":", collapse = ", ")
    # Add extra line space
    parameterStr = gsub(", nBackground:", ",\nnBackground:", parameterStr, fixed = TRUE)
    titleCur = paste0(prefixTitle, " for ontology ", type, "\n(", parameterStr, ")") 
    
    pValuePrefix = "Raw "
    if (display_pAdj & !stringr::str_starts(type, "GO_")) {
        dataCur = dataCur %>%
            dplyr::mutate(pval = .data$p.adjust)
        
        pValuePrefix = "Adj."
    }
    
    
    dataCur = dataCur %>%
        dplyr::mutate(dataCur, Term.mod = stringr::str_trunc(as.character(.data$Term), width = maxWidth_nchar_plot, side = "right")) %>%
        dplyr::mutate(pval = as.numeric(gsub(">|<", "", .data$pval))) %>%
        dplyr::arrange(.data$pval) %>%
        dplyr::filter(.data$pval <= p) # filter out non significant results and of the remaining take the top n
    
    legendsTitle = paste0(pValuePrefix, " p-value")
    labelAxis = paste0("(Truncated) ", type, " Term")
    if (nrow(dataCur) > topn_pvalue) {
        labelAxis = paste0(labelAxis, " (top ", topn_pvalue, " terms only)")
    }  
    
    dataCur = dataCur %>%
        dplyr::slice(seq_len(topn_pvalue)) %>%
        dplyr::arrange(.data$GeneRatio)
    #dplyr::top_n(n = topn_pvalue, wt = -pval)
    
    
    
    if (nrow(dataCur) > 0) {
        averagePvalue = mean(dataCur$pval, na.rm = TRUE)
        
        g = ggplot2::ggplot(dataCur , ggplot2::aes(x = stats::reorder(.data$Term, .data$GeneRatio), y = .data$GeneRatio, colour = .data$pval, size = .data$Found) ) +
            ggplot2::geom_point(na.rm = TRUE) +
            ggplot2::scale_x_discrete(labelAxis, labels = dataCur$Term.mod) +
            ggplot2::scale_color_gradient2(legendsTitle, midpoint = averagePvalue, low = "#F21A00" , mid = "#EBCC2A", high = "#3B9AB2") +
            ggplot2::scale_size_continuous("Number of\ngenes found (n)") + 
            ggplot2::ggtitle(titleCur) +
            ggplot2::ylab(paste0("Gene ratio [n / total foreground (", paramsCur$nForeground, ")]")) +
            ggplot2::coord_flip() +
            ggplot2::theme_bw() +
            ggplot2::theme(plot.title = ggplot2::element_text(size = ggplot2::rel(0.9)))
        
        plot(g)
    } else {
        
        plot(c(0, 1), c(0, 1), ann = FALSE, bty = 'n', type = 'n', xaxt = 'n', yaxt = 'n', main = title)
        
        message = paste0(titleCur, "\n\nNo enrichment found.")  
        if (pValuePrefix == "Adj.") {
            message = paste0(message, "\n\nYou may run again based on\nraw p-value and not adjusted one.")
        }  
        
        text(x = 0.5, y = 0.5, message, cex = 1.6, col = "red")
        
    }
    
    
    
}


#' Plot general structure & connectivity statistics for each community in a filtered \code{\linkS4class{GRN}}
#' 
#' Similarly to the statistics produced by \code{\link{plotGeneralGraphStats}}, summaries regarding the vertex degrees and the most important vertices per community are generated. Note that the communities need to first be calculated using the \code{\link{calculateCommunitiesStats}} function
#'
#' @template GRN
#' @template outputFolder
#' @template plotAsPDF
#' @template pdf_width
#' @template pdf_height
#' @template pages
#' @template basenameOutput
#' @inheritParams plotCommunitiesEnrichment
#' @template communities
#' @template forceRerun
#' @param topnGenes Integer > 0. Default 20. Number of genes to plot, sorted by their rank or label.
#' @param topnTFs Integer > 0. Default 20. Number of TFs to plot, sorted by their rank or label.
#' @return The same \code{\linkS4class{GRN}} object, without modifications.
#' @seealso \code{\link{plotGeneralGraphStats}}
#' @seealso \code{\link{calculateCommunitiesStats}}
#' @seealso \code{\link{calculateCommunitiesEnrichment}}
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plotCommunitiesStats(GRN, plotAsPDF = FALSE, pages = 1)
#' @export
plotCommunitiesStats <- function(GRN, outputFolder = NULL, basenameOutput = NULL, 
                                 selection = "byRank", communities = seq_len(5), 
                                 topnGenes = 20, topnTFs = 20, 
                                 plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12, pages = NULL,
                                 forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    
    checkmate::assertIntegerish(topnGenes, lower = 1)
    checkmate::assertIntegerish(topnTFs, lower = 1)
    checkmate::assertChoice(selection , c("byRank", "byLabel"))
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    
    .checkGraphExistance(GRN)
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    fileCur = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_communityStats"), basenameOutput), ".pdf")
    
    if (!file.exists(fileCur) | forceRerun | !plotAsPDF) {
        
        GRN = .addFunctionLogToObject(GRN)
        
        if (plotAsPDF) {
            .checkOutputFile(fileCur)
            grDevices::pdf(fileCur, width = pdf_width, height = pdf_height)
            futile.logger::flog.info(paste0("Plotting to file ", fileCur))
        } else {
            futile.logger::flog.info(paste0("Plotting directly"))
        }
        
        pageCounter = 1  
        
        vertexMetadata = as.data.frame(igraph::vertex.attributes(GRN@graph$TF_gene$graph))
        
        
        if (is.null(vertexMetadata$community)) {
            
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                g = ggplot2::ggplot() + ggplot2::annotate("text", x = 4, y = 25, size = 5, color = "red", 
                                                          label = "Nothing to plot:\nNo communities found") + ggplot2::theme_void()
                plot(g)
            } 
            
            pageCounter = pageCounter + 1 
            
            message = "plotCommunitiesStats: No communities found in GRN object. Make sure the filtered connections contain also genes."
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
            
        } else {
            
            communitiesDisplay = .selectCommunities(GRN, selection, communities, refAll = "graph", graph = "TF_gene") 
            
            
            communityVertices = vertexMetadata %>%
                dplyr::filter(.data$community %in% communitiesDisplay) %>%
                dplyr::mutate(Class = dplyr::if_else(.data$isTF, "TF", "gene"))
            
            
            
            if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                
                # Class: TF or gene
                gCommunityVertices = ggplot2::ggplot(communityVertices, ggplot2::aes(x = .data$community, fill = .data$Class)) +
                    ggplot2::geom_bar(position = "stack") +
                    ggplot2::ggtitle("Vertices per community") +
                    ggplot2::xlab("Community") +
                    ggplot2::ylab("Vertex count") +
                    ggplot2::scale_fill_manual(values = c("#3B9AB2", "#E1AF00")) + 
                    ggplot2::theme_bw()
                plot(gCommunityVertices)
            } 
            pageCounter = pageCounter + 1 
            
            
            for (communityCur in as.character(communitiesDisplay)) { # change this to select communities
                
                communityVerticesCur = communityVertices %>%
                    dplyr::filter(.data$community == communityCur) %>%
                    dplyr::pull(.data$name)
                
                community_subgraph.df = 
                    igraph::induced_subgraph(graph = GRN@graph$TF_gene$graph, 
                                             vids = communityVerticesCur) %>%
                    igraph::as_long_data_frame() %>% 
                    dplyr::select("from_name", "to_name", "connectionType", "from_names_TF_all", "to_names_gene") %>%
                    dplyr::rename(V1 = "from_name", V2 = "to_name", V1_name = "from_names_TF_all", V2_name = "to_names_gene") %>%
                    dplyr::mutate(community = communityCur, isTFTFinteraction = FALSE)
                
                # Handle cases where TF-TF interactions occur and the V2_name is NA because the gene name is NA
                allTFs = GRN@graph$TF_gene$table$V1
                index_V2_isTF = which(community_subgraph.df$V2 %in% allTFs)
                community_subgraph.df$isTFTFinteraction[index_V2_isTF] = TRUE
                match_TF_name = match(community_subgraph.df$V2[index_V2_isTF], vertexMetadata$name)
                # Change only those V2_names for which V2 is a TF, put the TF name in this case
                community_subgraph.df$V2_name[index_V2_isTF] = vertexMetadata$names_TF_all[match_TF_name]
                
                
                community.degreeStats = .getDegreeStats(GRN, community_subgraph.df, 
                                                        nCentralGenes = topnGenes,
                                                        nCentralTFs = topnTFs)
                
                
                # Only needed here temporarily
                # futile.logger::flog.info(paste0("Generating community graph..."))
                
                GRN@graph$communitySubgraph = list() 
                GRN@graph$communitySubgraph$table = community_subgraph.df
                GRN@graph$communitySubgraph$graph = .buildGraph(GRN@graph$communitySubgraph$table, 
                                                                directed = GRN@graph$parameters$directed, 
                                                                allowLoops = GRN@graph$parameters$allowLoops, 
                                                                removeMultiple = GRN@graph$parameters$removeMultiple,
                                                                silent = TRUE)
                
                community.eigenStats = .getEigenCentralVertices(GRN, graphType = "communitySubgraph", 
                                                                nCentralGenes = topnGenes,
                                                                nCentralTFs = topnTFs
                )
                GRN@graph$communitySubgraph = NULL
                
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    gDegreeDist = community.degreeStats$figures$degreeDist + ggplot2::ggtitle("Degree Distribution")
                    
                    
                    plot(gDegreeDist  + 
                             patchwork::plot_annotation(title = paste0("Community ", communityCur)) )
                }
                pageCounter = pageCounter + 1 
                
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    gTopGenes   = community.degreeStats$figures$topGenes   + ggplot2::ggtitle(paste0("Top ", topnGenes," degree-central genes"))
                    gTopTFs     = community.degreeStats$figures$topTFs     + ggplot2::ggtitle(paste0("Top ", topnTFs  ," degree-central TFs"))
                    
                    gTopEigenGenes = community.eigenStats$topGenes + ggplot2::ggtitle(paste0("Top ", topnGenes," eigenvector-central genes"))
                    gTopEigenTFs   = community.eigenStats$topTFs   + ggplot2::ggtitle(paste0("Top ", topnTFs,  " eigenvector-central TFs"))   
                    
                    plot((gTopGenes | gTopTFs) / (gTopEigenGenes | gTopEigenTFs) + 
                             patchwork::plot_annotation(title = paste0("Community ", communityCur)) )
                } 
                pageCounter = pageCounter + 1 
                
            } # end for each community
            
        }
        
        .checkPageNumberValidity(pages, pageCounter)
        if (plotAsPDF) grDevices::dev.off()
        
    }  else {
        .printDataAlreadyExistsMessage()
    }
    
    .printExecutionTime(start)
    
    GRN
}

#' Plot community-based enrichment results for a filtered \code{\linkS4class{GRN}} object
#' 
#' Similarly to \code{\link{plotGeneralEnrichment}} and \code{\link{plotTFEnrichment}}, the results of the community-based enrichment analysis are plotted.
#' This function produces multiple plots. First, one plot per community to summarize the community-specific enrichment.
#' Second, a summary heatmap of all significantly enriched terms across all communities and for the whole eGRN. The latter allows to compare the results with the general network enrichment.
#' Third, a subset of the aforementioned heatmap, showing only the top most significantly enriched terms per community and for the whole eGRN (as specified by \code{nID}) for improved visibility 
#' 
#' @inheritParams plotGeneralEnrichment
#' @param selection Character. Default \code{"byRank"}. One of: \code{"byRank"}, \code{"byLabel"}. 
#' Specify whether the communities will be selected based on their rank or explicitly by their label. 
#' Note that the label is independent of the rank. When set to \code{"byRank"}, the largest 
#' community (with most vertices) always has a rank of 1. 
#' @template communities
#' @param nSignificant Numeric > 0. Default 3. Threshold to filter out an ontology term with less than \code{nSignificant} overlapping genes. 
#' @param nID Numeric > 0. Default 10. For the reduced summary heatmap, number of top terms to select per community / for the general enrichment.
#' @template maxWidth_nchar_plot
#' @return  The same \code{\linkS4class{GRN}} object, without modifications.
#' @seealso \code{\link{plotGeneralEnrichment}}
#' @seealso \code{\link{plotTFEnrichment}}
#' @seealso \code{\link{calculateCommunitiesEnrichment}}
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plotCommunitiesEnrichment(GRN, plotAsPDF = FALSE, pages = 1)
#' @export
#' @import ggplot2
#' @importFrom grid gpar
plotCommunitiesEnrichment <- function(GRN, outputFolder = NULL, basenameOutput = NULL, 
                                      selection = "byRank", communities = NULL,
                                      topn_pvalue = 30, p = 0.05, nSignificant = 2, nID = 10, maxWidth_nchar_plot = 50,
                                      display_pAdj = FALSE,
                                      plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12, pages = NULL,
                                      forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assertChoice(selection , c("byRank", "byLabel"))
    checkmate::assertNumeric(p, lower = 0, upper = 1)
    checkmate::assertIntegerish(topn_pvalue, lower = 1)
    checkmate::assertNumeric(nSignificant, lower = 1)
    checkmate::assertNumeric(nID, lower = 1)
    checkmate::assertFlag(display_pAdj)
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertIntegerish(maxWidth_nchar_plot, lower = 10)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(forceRerun)
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    
    .checkGraphExistance(GRN)
    
    if (is.null(GRN@stats$Enrichment$byCommunity)) {
        message = "No communities found, cannot calculate enrichment. Please run the functioncalculateCommunitiesStats first. If you did already, it looks like no communities could be identified before"
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    }
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    fileCur = paste0(outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_communityEnrichment"), basenameOutput), ".pdf")
    
    if (!file.exists(fileCur) | forceRerun | !plotAsPDF) {
        
        futile.logger::flog.info(paste0("Including terms only if overlap is at least ", nSignificant, " genes.")) 
        GRN = .addFunctionLogToObject(GRN)
        
        if (is.null(GRN@stats$Enrichment$general)) {
            message = paste0("Could not find general enrichment analysis. Please run the function calculateGeneralEnrichment first.")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
        } 
        
        GRN@stats$Enrichment$byCommunity$combined = NULL
        
        communitiesDisplay = .selectCommunities(GRN, selection, communities, refAll = "enrichment", graph = "TF_gene") 
        
        
        futile.logger::flog.info(paste0(" Plotting the enrichment for the following communities: ", paste0(communitiesDisplay, collapse = ",")))
        
        # Preparing the heatmap, has to be calculated only once
        vertexMetadata = as.data.frame(igraph::vertex.attributes(GRN@graph$TF_gene$graph))
        # Get the number of vertexes per community as additional annotation column for the heatmap
        geneCounts = vertexMetadata %>%
            dplyr::select("name", "community") %>%
            dplyr::distinct() %>%
            dplyr::count(.data$community)
        
        
        allOntologies.l = .checkEnrichmentCongruence_general(GRN, type = "community")
        
        # Reset, as this is created anew and otherwise raises a warning due to the missing "result" slot
        GRN@stats$Enrichment$byCommunity[["combined"]] = list()
        
        
        if (plotAsPDF) {
            .checkOutputFile(fileCur)
            grDevices::pdf(fileCur, width = pdf_width, height = pdf_height)
            futile.logger::flog.info(paste0("Plotting to file ", fileCur))
        } else {
            futile.logger::flog.info(paste0("Plotting directly"))
        }
        
        pageCounter = 1
        
        for (ontologyCur in allOntologies.l$community) {
            
            futile.logger::flog.info(paste0(" Plotting results for ontology ", ontologyCur))
            
            pValPrefix = .getPValPrefix(display_pAdj, ontologyCur)
            
            for (communityCur in as.character(communitiesDisplay)) {
                
                if (is.null(GRN@stats$Enrichment$byCommunity[[communityCur]][[ontologyCur]])) {
                    # GRN = calculateCommunitiesEnrichment(GRN = GRN, selection = "byLabel", communities = communityCur)
                    message = paste0("Could not find community enrichment results for ", ontologyCur, " for community ", communityCur, ". Please run calculateCommunitiesEnrichment first or change the communities to plot")
                    .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
                }
                
                
                # dataCur this contains all ontologies, and within each ontology, 2 slots: results, parameters
                dataCur = GRN@stats$Enrichment$byCommunity[[communityCur]] [[ontologyCur]]
                titleCur = paste0("Enrichment Analysis - Community ", communityCur)
                
                if (is.null(dataCur)) {
                    message = paste0("Could not find enrichment results for ontology ", ontologyCur, " and community ", communityCur, ". Please rerun the function calculateCommunitiesEnrichment.")
                    .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
                }
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    .plotEnrichmentGeneral(dataCur, ontologyCur, titleCur, 
                                           topn_pvalue = topn_pvalue, p = p, display_pAdj = display_pAdj)
                }
                
                pageCounter = pageCounter + 1
                
            } # end for all communities 
            
            # Summary heatmap to compare terms enriched in the general network to their enrichment in the communities:
            # Currently assumes that the general enrichment has been run
            if (ontologyCur %in% allOntologies.l$all) {
                
                # Take one community as example for which ontologies have been produced
                GRN@stats$Enrichment$byCommunity[["combined"]][[ontologyCur]] = 
                    .combineEnrichmentResults(GRN, type = "byCommunity", ontologyCur, 
                                              p = p, nSignificant = nSignificant, display_pAdj) %>%
                    dplyr::filter(.data$community %in% c("all", as.character(communitiesDisplay)))
                
                # It can happen that some TFs are filtered out here because all terms are not significant. Thus, even though
                # 5 TFs have been requested only 4 actually show any enriched terms
                
                if (nrow(GRN@stats$Enrichment$byCommunity[["combined"]][[ontologyCur]]) == 0) {
                    message = paste0("plotCommunitiesEnrichment: No enrichment terms passed the filters when creating the across-community summary plot for ontology ", ontologyCur, ". Skipping. You may adjust the parameter nSignificant to a lower value")
                    .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
                    next
                }
                
                
                
                # Convert to wide format and filter those terms that are significant at least once
                all.df.wide = GRN@stats$Enrichment$byCommunity[["combined"]][[ontologyCur]] %>% 
                    dplyr::select("community", "ID", "pval") %>%
                    tidyr::pivot_wider(names_from = "community", values_from = "pval") %>%
                    dplyr::mutate_at(dplyr::vars(!dplyr::contains("ID")), as.numeric) %>%
                    dplyr::rowwise() %>%
                    dplyr::mutate(nSig = sum(dplyr::c_across(where(is.numeric)) <= p, na.rm = TRUE)) %>% # In how many columns significant?
                    dplyr::ungroup() %>%
                    dplyr::filter(.data$nSig > 0)
                
                # In extreme cases, it could happen that the "all" community is missing because there are no enrichments whatsoever.
                # In this case, we need to add it back here 
                if (!"all" %in% colnames(all.df.wide)) {
                    all.df.wide = dplyr::mutate(all.df.wide, all = NA)
                }
                
                # sort by community size
                communities.order = c("all", intersect(.selectCommunitesByRank(GRN , communities = NULL), colnames(all.df.wide)))
                
                
                matrix.m = all.df.wide %>%
                    dplyr::mutate(Term = GRN@stats$Enrichment$byCommunity[["combined"]][[ontologyCur]]$Term[match(.data$ID, GRN@stats$Enrichment$byCommunity[["combined"]][[ontologyCur]]$ID)]) %>%
                    dplyr::filter(!is.na(.data$Term)) %>%
                    dplyr::select("Term", dplyr::any_of(communities.order)) %>% # reorder the table based on the previously generated custom order
                    dplyr::mutate_at(dplyr::vars(!dplyr::contains("Term")), function(x) {return(-log10(x))}) %>%
                    tibble::column_to_rownames("Term") %>%
                    as.matrix()
                
                
                geneCounts_communities = geneCounts %>%
                    dplyr::filter(.data$community %in% as.character(colnames(matrix.m)),
                                  .data$community %in% geneCounts$community) %>%
                    dplyr::arrange(dplyr::desc(.data$n))
                
                # Sanity check: Except for the first item "all", they should all be identical
                stopifnot(identical(as.character(geneCounts_communities$community), colnames(matrix.m)[-1]))
                
                # Common heatmap parameters for both p1 and p2
                top_annotation = ComplexHeatmap::HeatmapAnnotation(
                    nGenes = ComplexHeatmap::anno_barplot(
                        x = c(sum(geneCounts$n), geneCounts_communities$n), 
                        border = FALSE,  bar_width = 0.8,  gp = grid::gpar(fill = "#046C9A")),
                    annotation_name_gp = grid::gpar(fontsize = 9), annotation_name_side = "left", annotation_name_rot = 90)
                
                axixStr = paste0("-log10\n(", pValPrefix, "p-value)")
                colors_pvalue = viridis::viridis(n = 30, direction = -1)
                
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    futile.logger::flog.info(paste0("  Including ", nrow(matrix.m), " terms in the full summary heatmap and " , ncol(matrix.m), " columns"))
                    
                    p1 = .drawCombinedHeatmap(matrix = matrix.m, pdf_width = pdf_width, pdf_height = pdf_height,
                                              name = axixStr, 
                                              maxWidth_nchar_plot = maxWidth_nchar_plot,
                                              colors_pvalue = colors_pvalue, 
                                              column_title = paste0("Summary of all significantly enriched terms\nacross all communities and overall (Ontology: ", ontologyCur,")"),
                                              top_annotation = top_annotation)
                    
                    print(p1)
                    
                }
                pageCounter = pageCounter + 1
                
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    # Now focus on the top X only per community
                    ID_subset =  GRN@stats$Enrichment$byCommunity[["combined"]][[ontologyCur]] %>% 
                        dplyr::group_by(.data$community) %>% 
                        dplyr::arrange(.data$pval) %>% 
                        dplyr::slice(seq_len(nID)) %>%
                        dplyr::pull(.data$ID) %>% as.character()
                    
                    
                    matrix.m = all.df.wide %>%
                        dplyr::filter(.data$ID %in% ID_subset) %>%
                        dplyr::mutate(Term = GRN@stats$Enrichment$byCommunity[["combined"]][[ontologyCur]]$Term[match(.data$ID, GRN@stats$Enrichment$byCommunity[["combined"]][[ontologyCur]]$ID)]) %>%
                        dplyr::filter(!is.na(.data$Term)) %>%
                        dplyr::select(-"nSig", -"ID") %>%
                        tibble::column_to_rownames("Term") %>%
                        dplyr::mutate_at(dplyr::vars(!dplyr::contains("ID")), function(x) {return(-log10(x))}) %>%
                        dplyr::select(dplyr::any_of(communities.order)) %>% # reorder based on the previously generated custom order
                        as.matrix()
                    
                    futile.logger::flog.info(paste0("  Including ", nrow(matrix.m), " terms in the reduced summary heatmap and " , ncol(matrix.m), " columns"))
                    
                    
                    p2 = .drawCombinedHeatmap(matrix = matrix.m, pdf_width = pdf_width, pdf_height = pdf_height, 
                                              name = axixStr, 
                                              maxWidth_nchar_plot = maxWidth_nchar_plot,
                                              colors_pvalue = colors_pvalue, 
                                              column_title = paste0("Summary of top ", nID, " enriched terms\nper community and overall (Ontology: ", ontologyCur,")"),
                                              top_annotation = top_annotation)
                    print(p2)
                } 
                pageCounter = pageCounter + 1
                
            } else {
                message = paste0("plotCommunitiesEnrichment: Cannot include ontology ", ontologyCur, " in overview with general enrichment because it is missing in the latter. Please rerun the function calculateGeneralEnrichment for the aforementioned ontology.")
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
            } 
            
            
        } # end for all ontologies
        
        
        .checkPageNumberValidity(pages, pageCounter)
        if (plotAsPDF) grDevices::dev.off()
        
    }  else {
        .printDataAlreadyExistsMessage()
    }
    
    .printExecutionTime(start)
    
    GRN
    
}


.drawCombinedHeatmap <- function(matrix, pdf_width, pdf_height, name, maxWidth_nchar_plot, colors_pvalue, column_title, top_annotation) {
    
    nTerms = nrow(matrix)
    
    # TODO: very crude so far, and not dependent on the pdf_height
    fontsize_row_names_gp = dplyr::case_when(
        nTerms < 20  ~ 12,
        nTerms < 40  ~ 10,
        nTerms < 50  ~ 8,
        nTerms < 80  ~ 6,
        nTerms < 110  ~ 5,
        nTerms < 140  ~ 4,
        nTerms < 200  ~ 2,
        TRUE ~ 1
    )
    
    # TODO:
    fontsize_column_names_gp = dplyr::case_when(
        TRUE ~ 8
    )
    
    # Prevent :Error: You should have at least two distinct break values.
    if (unique(matrix) %>% as.vector() %>% length() == 1) {
        colors_pvalue = colors_pvalue[1]
    }
    
    # Make the row names shorter and unique
    rownames(matrix) = make.unique(stringr::str_trunc(rownames(matrix), width = maxWidth_nchar_plot, side = "right")) 
    
    
    ComplexHeatmap::Heatmap(
        matrix,
        # Do NOT set the width here, this messes it up somehow, seems to be much better when omitting it
        #  heatmap_width  = unit(pdf_width, "cm"),
        #  heatmap_height = unit(pdf_height, "cm"),
        name = name,
        col = colors_pvalue,
        cluster_columns = FALSE, cluster_rows = FALSE,
        row_names_side = "left", row_names_gp = grid::gpar(fontsize = fontsize_row_names_gp), 
        column_title = column_title,
        column_names_gp = grid::gpar(fontsize = fontsize_column_names_gp),
        # heatmap annotation: bar plot displaying the number of genes in each subgroup
        top_annotation = top_annotation,
        row_names_max_width = ComplexHeatmap::max_text_width(
            rownames(matrix), 
            gp = grid::gpar(fontsize = fontsize_row_names_gp)
        )
    )
    
    
    
}



#' Plot TF-based GO enrichment results
#' 
#' Similarly to \code{\link{plotGeneralEnrichment}} and \code{\link{plotCommunitiesEnrichment}}, the results of the TF-based enrichment analysis are plotted.
#' This function produces multiple plots. First, one plot per community to summarize the TF-specific enrichment.
#' Second, a summary heatmap of all significantly enriched terms across all TFs and for the whole eGRN. The latter allows to compare the results with the general network enrichment.
#' Third, a subset of the aforementioned heatmap, showing only the top most significantly enriched terms per TF and for the whole eGRN (as specified by \code{nID}) for improved visibility .
#' 
#' 
#' @inheritParams plotGeneralEnrichment
#' @inheritParams plotCommunitiesEnrichment
#' @param TF.IDs \code{NULL} or character vector. Default \code{NULL}. For \code{rankType="custom"} the IDs of the TFs to plot. Ignored otherwise.
#' @param rankType Character. One of: "degree", "EV", "custom". This parameter will determine the criterion to be used to identify the "top" nodes. If set to "degree", the function will select top nodes based on the number of connections they have, i.e. based on their degree-centrality. If set to "EV" it will select the top nodes based on their eigenvector-centrality score in the network.
#' @param n NULL or numeric. Default NULL. If set to NULL, all previously calculated TF enrichments will be plotted. If set to a value between (0,1), it is treated as a percentage of top nodes. If the value is passed as an integer it will be treated as the number of top nodes. This parameter is not relevant if rankType = "custom".
#' @return The same \code{\linkS4class{GRN}} object, without modifications.
#' @seealso \code{\link{plotGeneralEnrichment}}
#' @seealso \code{\link{plotCommunitiesEnrichment}}
#' @seealso \code{\link{calculateTFEnrichment}}
#' @examples 
#' # See the Workflow vignette on the GRaNIE website for examples
#' GRN = loadExampleObject()
#' GRN = plotTFEnrichment(GRN, n = 5, plotAsPDF = FALSE, pages = 1)
#' @export
#' @importFrom grid gpar
plotTFEnrichment <- function(GRN, rankType = "degree", n = NULL, TF.IDs = NULL,
                             topn_pvalue = 30, p = 0.05, 
                             nSignificant = 2, nID = 10,
                             display_pAdj = FALSE,
                             maxWidth_nchar_plot = 50,
                             outputFolder = NULL, 
                             basenameOutput = NULL, 
                             plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12, pages = NULL,
                             forceRerun = FALSE) {
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assertChoice(rankType, c("degree", "EV", "custom"))
    checkmate::assert(checkmate::checkNull(n), checkmate::checkNumeric(n))
    checkmate::assertNumeric(p, lower = 0, upper = 1)
    checkmate::assertNumeric(topn_pvalue, lower = 1)
    checkmate::assertNumeric(nSignificant, lower = 1)
    checkmate::assertNumeric(nID, lower = 1)
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assert(checkmate::checkNull(pages), checkmate::checkIntegerish(pages, lower = 1))
    checkmate::assertFlag(display_pAdj)
    checkmate::assertIntegerish(maxWidth_nchar_plot, lower = 10)
    checkmate::assert(checkmate::checkNull(outputFolder), checkmate::checkCharacter(outputFolder, min.chars = 1))
    checkmate::assert(checkmate::checkNull(basenameOutput), checkmate::checkCharacter(basenameOutput, len = 1, min.chars = 1, any.missing = FALSE))
    checkmate::assertFlag(forceRerun)
    
    .checkGraphExistance(GRN)
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    if (rankType == "custom") {
        if (is.null(TF.IDs)) {
            message = "To plot the GO enrichment for a custom set of TFs, you must provide the TF IDs in the 'TF.IDs' parameter."
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
        }
        wrongTFs = setdiff(TF.IDs, unique(GRN@connections$all.filtered$`0`$TF.ID))
        if (length(wrongTFs) > 0) {
            message = paste0("plotTFEnrichment: The following user-provided TF(s) \"",  paste0(wrongTFs, collapse = " + "), "\" are not in the filtered GRN. They will be ommited from the results.")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
            
        }
        TFset = setdiff(TF.IDs, wrongTFs) 
        
        if (length(TFset) == 0) {
            message = paste0("There are no TFs left to show. Adjust the custom TF list.")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
            
        }
        
    } else{ #rankType = "degree"
        
        if (is.null(n)) {
            TFset = setdiff(names(GRN@stats$Enrichment$byTF), "combined")
        } else {
            TFset = getTopNodes(GRN, nodeType = "TF", rankType = rankType, n = n) %>% dplyr::pull(.data$TF.ID)
        }
        
    }
    
    fileCur = paste0(outputFolder, 
                     ifelse(is.null(basenameOutput), .getOutputFileName("plot_TFEnrichment"), basenameOutput), 
                     ".pdf")
    
    if (!file.exists(fileCur) | forceRerun | !plotAsPDF) {
        
        GRN = .addFunctionLogToObject(GRN)
        
        allOntologies.l = .checkEnrichmentCongruence_general(GRN, type = "TF")
        
        if (is.null(allOntologies.l$TF)) {
            message = paste0("No enrichment results found for TFs. Run the function calculateTFEnrichment first")
            .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
        }
        
        # Get the number of vertexes per TF as additional annotation column for the heatmap
        vertexMetadata = as.data.frame(igraph::vertex.attributes(GRN@graph$TF_gene$graph))
        nodeDegree = igraph::degree(GRN@graph$TF_gene$graph)
        
        nodeDegree_TFset = dplyr::left_join(GRN@annotation$TFs, 
                                            as.data.frame(nodeDegree) %>% tibble::rownames_to_column("TF.ENSEMBL"), by = "TF.ENSEMBL") %>%
            dplyr::filter(.data$TF.ID %in% as.character(TFset))
        
        pageCounter = 1  
        
        
        if (plotAsPDF) {
            .checkOutputFile(fileCur)
            grDevices::pdf(fileCur, width = pdf_width, height = pdf_height)
            futile.logger::flog.info(paste0("Plotting to file ", fileCur))
        } else {
            futile.logger::flog.info(paste0("Plotting directly"))
        }
        
        for (ontologyCur in allOntologies.l$TF) {
            
            futile.logger::flog.info(paste0(" Plotting results for ontology ", ontologyCur))
            
            pValPrefix = .getPValPrefix(display_pAdj, ontologyCur)
            
            if (display_pAdj && stringr::str_starts(ontologyCur, "GO_")) {
                message = paste0("plotTFEnrichment: For ontology ", ontologyCur, ", only raw p-values can be displayed. Adjusted p-values are only available for the following ontologies: KEGG, Disease Ontology, Reactome")
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
            }
            
            
            for (TFCur in as.character(TFset)) {
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    TF.ENSEMBL = GRN@annotation$TFs %>% dplyr::filter(.data$TF.ID == TFCur) %>% dplyr::pull(TF.ENSEMBL)
                    
                    TF.ID.full = .printTF(GRN, TFCur, printEnsemblID = TRUE)
                    
                    futile.logger::flog.info(paste0("  TF ", TF.ID.full))
                    
                    # if the enrichment slot for the TFs is empty, calculate the enrichment
                    if (is.null(GRN@stats$Enrichment[["byTF"]][[TFCur]])) { 
                        message = paste0("Could not find TF enrichment results for TF ", TFCur, ". Run the function calculateTFEnrichment first or make sure the parameter n that was used for calculateTFEnrichmentn was larger or equal with the current value for n for this function.")
                        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
                    }
                    
                    dataCur = GRN@stats$Enrichment[["byTF"]][[TFCur]][[ontologyCur]]
                    if (is.null(dataCur)) {
                        message = paste0("Could not find enrichment results for ontology ", ontologyCur, " and TF ", TFCur, ". Rerun the function calculateTFEnrichment.")
                        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
                    }
                    titleCur = paste0("Enrichment Analysis - TF: ", TF.ID.full)
                    .plotEnrichmentGeneral(data = dataCur, type = ontologyCur, prefixTitle = titleCur,
                                           topn_pvalue = topn_pvalue, p = p, display_pAdj = display_pAdj)
                    
                    
                }
                pageCounter = pageCounter + 1
                
            }
            
            if (ontologyCur %in% allOntologies.l$all) {
                
                
                # Summary heatmap to compare terms enriched in the general network to their enrichment in the communities:
                # Currently assumes that the general enrichment has been run
                
                # Take one community as example for which ontologies have been produced
                GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]] = 
                    .combineEnrichmentResults(GRN, type = "byTF", ontology = ontologyCur, 
                                              p = p, nSignificant = nSignificant, display_pAdj) %>%
                    dplyr::filter(.data$TF.ID %in% c("all", as.character(TFset)))
                
                if (nrow(GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]]) == 0) {
                    message = paste0("plotTFEnrichment: No enrichment terms passed the filters when creating the across-community summary plot for ontology ", ontologyCur, ". Skipping. You may adjust the parameter nSignificant to a lower value")
                    .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
                    next
                }
                
                
                # Convert to wide format and filter those terms that are significant at least once
                all.df.wide = GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]] %>% 
                    dplyr::select("TF.ID", "ID", "pval") %>%
                    tidyr::pivot_wider(names_from = "TF.ID", values_from = "pval") %>%
                    dplyr::mutate_at(dplyr::vars(!dplyr::contains("ID")), as.numeric) %>%
                    dplyr::rowwise() %>%
                    dplyr::mutate(nSig = sum(dplyr::c_across(where(is.numeric)) <= p, na.rm = TRUE)) %>%
                    dplyr::ungroup() %>%
                    dplyr::filter(.data$nSig > 0)
                
                # In extreme cases, it could happen that the "all" community is missing because there are no enrichments whatsoever.
                # In this case, we need to add it back here 
                if (!"all" %in% colnames(all.df.wide)) {
                    all.df.wide = dplyr::mutate(all.df.wide, all = NA)
                }
                
                
                TF.order = c("all", setdiff(unique(GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]]$TF.ID), "all"))
                
                
                matrix.m = all.df.wide %>%
                    dplyr::mutate(Term = GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]]$Term[match(.data$ID, GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]]$ID)]) %>%
                    dplyr::filter(!is.na(.data$Term)) %>%
                    dplyr::select("Term", dplyr::all_of(TF.order)) %>% # reorder the table based on the previously generated custom order
                    dplyr::mutate_at(dplyr::vars(!dplyr::contains("Term")), function(x) {return(-log10(x))}) %>%
                    # dplyr::mutate(Term = stringr::str_trunc(as.character(Term), width = maxWidth_nchar_plot, side = "right")) %>%
                    tibble::column_to_rownames("Term") %>%
                    as.matrix()
                
                
                # Make sure the top annotation has the same dimensionality as the resulting matrix
                nodeDegree_TFset_numbers =  nodeDegree_TFset %>%
                    dplyr::filter(.data$TF.ID %in% colnames(matrix.m)) %>%
                    dplyr::arrange(dplyr::desc(nodeDegree)) %>%
                    dplyr::pull(nodeDegree)
                
                top_annotation = ComplexHeatmap::HeatmapAnnotation(
                    nodeDegree = ComplexHeatmap::anno_barplot(
                        x = c("all" = nrow(vertexMetadata),  nodeDegree_TFset_numbers ), 
                        border = FALSE,  bar_width = 0.8,  gp = grid::gpar(fill = "#046C9A")),
                    annotation_name_gp = grid::gpar(fontsize = 5), annotation_name_side = "left", annotation_name_rot = 90)
                
                axixStr = paste0("-log10\n(", pValPrefix, "p-value)")
                
                colors_pvalue = viridis::viridis(n = 30, direction = -1)
                # Why colors here like this
                #colors_pvalue = c("#3B9AB2", "#9EBE91", "#E4B80E", "#F21A00")
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    futile.logger::flog.info(paste0("  Including ", nrow(matrix.m), " terms in the full heatmap and " , ncol(matrix.m), " columns"))
                    
                    p1 = .drawCombinedHeatmap(matrix = matrix.m, pdf_width = pdf_width, pdf_height = pdf_height,
                                              name = axixStr, 
                                              maxWidth_nchar_plot = maxWidth_nchar_plot,
                                              colors_pvalue = colors_pvalue, 
                                              column_title = paste0("Summary of all significantly enriched terms\nacross all TFs and overall (Ontology: ", ontologyCur,")"),
                                              top_annotation = top_annotation)
                    
                    print(p1)
                }
                pageCounter = pageCounter + 1
                
                if (is.null(pages) | (!is.null(pages) && pageCounter %in% pages)) {
                    
                    # Now focus on the top X only per community
                    ID_subset =  GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]] %>% 
                        dplyr::group_by(.data$TF.ID) %>% 
                        dplyr::arrange(.data$pval) %>% 
                        dplyr::slice(seq_len(nID)) %>%
                        dplyr::pull(.data$ID) %>% as.character()
                    
                    
                    matrix.m = all.df.wide %>%
                        dplyr::filter(.data$ID %in% ID_subset) %>%
                        dplyr::mutate(Term = GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]]$Term[match(.data$ID, GRN@stats$Enrichment$byTF[["combined"]][[ontologyCur]]$ID)]) %>%
                        dplyr::filter(!is.na(.data$Term)) %>%
                        dplyr::select(-"nSig", -"ID") %>%
                        tibble::column_to_rownames("Term") %>%
                        dplyr::mutate_at(dplyr::vars(!dplyr::contains("ID")), function(x) {return(-log10(x))}) %>%
                        dplyr::select(dplyr::all_of(TF.order)) %>% # reorder based on the previously generated custom order
                        as.matrix()
                    
                    futile.logger::flog.info(paste0("  Including ", nrow(matrix.m), " terms in the reduced summary heatmap and " , ncol(matrix.m), " columns"))
                    
                    p2 = .drawCombinedHeatmap(matrix = matrix.m, pdf_width = pdf_width, pdf_height = pdf_height, 
                                              name = axixStr, 
                                              maxWidth_nchar_plot = maxWidth_nchar_plot,
                                              colors_pvalue = colors_pvalue, 
                                              column_title = paste0("Summary of top ", nID, " enriched terms\n per TF and overall (Ontology: ", ontologyCur,")"),
                                              top_annotation = top_annotation)
                    
                    print(p2)
                }
                pageCounter = pageCounter + 1
                
            } else {
                message = paste0("plotTFEnrichment: Cannot include ontology ", ontologyCur, " in overview with general enrichment because it is missing in the latter. Please rerun the function calculateGeneralEnrichment for the aforementioned ontology.")
                .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
            } 
            
        } # end for all ontologies
        
        .checkPageNumberValidity(pages, pageCounter)
        if (plotAsPDF) grDevices::dev.off()
        
    }  else {
        .printDataAlreadyExistsMessage()
    }
    
    .printExecutionTime(start)
    
    GRN
    
}

.getDegreeStats <- function(GRN, df, nCentralGenes = 20, nCentralTFs = 20) {
    
    # This function takes as input the GRN and the graph in dataframe format 
    # returns histogram of degree distribution and plots of top degree-central TFs and genes
    
    TF.degrees = df %>%
        dplyr::filter(stringr::str_detect(.data$connectionType, "^tf")) %>% 
        dplyr::mutate(name_plot = paste0(.data$V1_name, "\n(", .data$V1, ")")) %>%
        dplyr::count(.data$name_plot, .data$V1) %>% 
        dplyr::rename(ID = "V1", ID_all = "name_plot", Degree = "n") 
    
    # TODO modify
    peak_TFend.degrees = df %>%
        dplyr::filter(stringr::str_detect(.data$connectionType, "peak$")) %>%
        dplyr::count(.data$V2) %>% dplyr::rename(ID = "V2", Degree = "n")
    
    peak_geneend.degrees = df %>%
        dplyr::filter(stringr::str_detect(.data$connectionType, "^peak")) %>%
        dplyr::count(.data$V1) %>% dplyr::rename(ID = "V1", Degree = "n")
    
    gene.degrees = df %>%
        dplyr::filter(stringr::str_detect(.data$connectionType, "gene$")) %>%
        dplyr::mutate(name_plot = paste0(.data$V2_name, "\n(", .data$V2, ")")) %>%  
        dplyr::count(.data$name_plot, .data$V2) %>% 
        dplyr::rename(ID = "V2", ID_all = "name_plot", Degree = "n")
    
    degrees.table = dplyr::bind_rows( TF = TF.degrees, 
                                      `peak (TF end)` = peak_TFend.degrees, 
                                      `peak (gene end)` = peak_geneend.degrees, 
                                      gene = gene.degrees,
                                      .id = "class") %>%
        dplyr::mutate(class = droplevels(factor(class, levels = c("TF", "peak (TF end)", "peak (gene end)", "gene")))) %>% # in case it was the TF-gene df that was passed, the peak levels will be dropped
        dplyr::arrange(class, dplyr::desc(.data$Degree))
    
    colours = c("TF" = "#E1AF00", "peak (TF end)" = "#F21A00", "peak (gene end)" = "#F21A00", "gene" = "#3B9AB2")
    
    ## General Degree Distribution ##
    gDegrees = ggplot2::ggplot(degrees.table, ggplot2::aes(x = .data$Degree, fill = class)) +
        ggplot2::geom_histogram(bins = 50) +
        ggplot2::scale_fill_manual(values = colours) +
        ggplot2::theme(legend.position = "none",  axis.text.x = ggplot2::element_text(angle = 45, vjust = 1, hjust = 1), 
                       strip.text = ggplot2::element_text(size = 8)) +
        ggplot2::ylab("Abundance") +
        ggplot2::facet_wrap(~class, labeller = ggplot2::labeller(class = .facetLabel(degrees.table)), scales = "free", ncol = 2) +
        ggplot2::theme_bw()
    
    ## Top degree-central TFs and Genes ##
    top.n.gene = degrees.table %>% 
        dplyr::filter(class == "gene") %>%
        dplyr::arrange(dplyr::desc(.data$Degree)) %>% 
        dplyr::slice(seq_len(nCentralGenes)) 
    
    top.n.tf = degrees.table %>% 
        dplyr::filter(class == "TF") %>%
        dplyr::rename(TF.ID = "ID") %>%
        dplyr::arrange(dplyr::desc(.data$Degree)) %>% 
        dplyr::slice(seq_len(nCentralTFs))
    
    theme_all = ggplot2::theme_bw() + ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, vjust = 1, hjust = 1, size = 8))
    
    gTopGenes = ggplot2::ggplot(data = top.n.gene, ggplot2::aes(x = stats::reorder(.data$ID_all, -.data$Degree), y = .data$Degree)) +
        ggplot2::geom_bar(stat = "identity", fill = "#3B9AB2") +
        ggplot2::geom_text(ggplot2::aes(label = .data$Degree), size = 3) + 
        ggplot2::xlab("Gene ID") +
        theme_all
    
    gTopTFs = ggplot2::ggplot(data = top.n.tf, ggplot2::aes(x = stats::reorder(.data$ID_all, -.data$Degree), y = .data$Degree)) +
        ggplot2::geom_bar(stat = "identity", fill = "#E1AF00") +
        ggplot2::geom_text(ggplot2::aes(label = .data$Degree), size = 3) + 
        ggplot2::xlab("TF ID") +
        theme_all
    
    results = list()
    results[["tbl"]][["degrees"]]        = degrees.table
    results[["figures"]][["degreeDist"]] = gDegrees
    results[["figures"]][["topGenes"]]   = gTopGenes
    results[["figures"]][["topTFs"]]     = gTopTFs
    
    return(results)
}

.getEigenCentralVertices <- function(GRN, graphType, nCentralGenes = 20, nCentralTFs = 20) {
    
    # returns a list of figures for the top eigenvector central Genes and TFs
    if (graphType == "TF_peak_gene") {
        geneSet = GRN@graph$TF_peak_gene$table %>%
            dplyr::filter(.data$connectionType == "peak-gene") %>%
            dplyr::pull(.data$V2) %>%
            unique()
        TFSet   = GRN@graph$TF_peak_gene$table %>%
            dplyr::filter(.data$connectionType == "tf-peak") %>%
            dplyr::pull(.data$V1) %>%
            unique()
        
    } else if (graphType == "TF_gene") {
        
        geneSet = unique(GRN@graph$TF_peak_gene$table$V2)
        TFSet   = unique(GRN@graph$TF_peak_gene$table$V1)
        
    } else if (graphType == "communitySubgraph") {
        # Also a TF-gene graph
        geneSet = unique(GRN@graph$communitySubgraph$table$V2)
        TFSet   = unique(GRN@graph$communitySubgraph$table$V1)
        
    } else {
        message = "Unknown graphType in.getEigenCentralVertices()"
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    }
    
    # get the centrality score of each vertex and extract top n genes and TFs
    eigen_ctr = igraph::eigen_centrality(GRN@graph[[graphType]]$graph)$vector
    
    
    nCentralGenes = seq_len(min(nCentralGenes, length(eigen_ctr[geneSet])))
    nCentralTFs   = seq_len(min(nCentralTFs  , length(eigen_ctr[TFSet])))
    
    top.n.genes = tibble::tibble(
        gene.ENSEMBL = names(sort(eigen_ctr[geneSet], decreasing = TRUE)[nCentralGenes]),
        Score = sort(eigen_ctr[geneSet], decreasing = TRUE)[nCentralGenes]) %>% 
        stats::na.omit() %>% # Remove NA rows in case there are not enough rows without NA
        dplyr::distinct() %>% 
        dplyr::left_join(GRN@graph[[graphType]]$table %>% dplyr::select("V2", "V2_name") %>% dplyr::distinct(), 
                         by = c("gene.ENSEMBL" = "V2") ) %>%
        #dplyr::mutate(gene.name = GRN@connections$all.filtered$`0`$gene.name[match(gene.ENSEMBL, GRN@connections$all.filtered$`0`$gene.ENSEMBL)])
        dplyr::rename(gene.name = "V2_name") %>%
        dplyr::mutate(name_plot = paste0(.data$gene.name, "\n(", .data$gene.ENSEMBL, ")")) %>%
        dplyr::filter(.data$Score > 0) # Require the score to be > 0 because we dont want top nodes to have a score of 0
    
    top.n.tf =  tibble::tibble(TF.ENSEMBL = names(sort(eigen_ctr[TFSet], decreasing = TRUE)[nCentralTFs]),
                               Score   =    sort(eigen_ctr[TFSet], decreasing = TRUE)[nCentralTFs]) %>% 
        stats::na.omit() %>% # Remove NA rows in case there are not enough rows without NA
        dplyr::distinct() %>%
        dplyr::left_join(GRN@graph[[graphType]]$table %>% dplyr::select("V1", "V1_name") %>% dplyr::distinct(), 
                         by = c("TF.ENSEMBL" = "V1") ) %>%
        dplyr::rename(TF.ID = "V1_name") %>%
        dplyr::mutate(name_plot = paste0(.data$TF.ID, "\n(", .data$TF.ENSEMBL, ")")) %>%
        dplyr::filter(.data$Score > 0) # Require the score to be > 0 because we dont want top nodes to have a score of 0
    
    # dplyr::mutate(TF.ID = GRN@connections$all.filtered$`0`$gene.name[match(gene.ENSEMBL, GRN@connections$all.filtered$`0`$gene.ENSEMBL)])
    
    theme_all = ggplot2::theme_bw() + ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, vjust = 1, hjust = 1, size = 8))
    
    
    gTopEigenGenes = ggplot2::ggplot(data = top.n.genes, ggplot2::aes(x = stats::reorder(.data$name_plot, -.data$Score), y = .data$Score)) +
        ggplot2::geom_bar(stat = "identity", fill = "#3B9AB2") +
        ggplot2::xlab("Gene name") +
        theme_all
    
    gTopEigenTFs = ggplot2::ggplot(data = top.n.tf, ggplot2::aes(x = stats::reorder(.data$name_plot, -.data$Score), y = .data$Score)) +
        ggplot2::geom_bar(stat = "identity", fill = "#E1AF00") +
        ggplot2::xlab("TF ID") +
        theme_all
    
    return(list(topGenes = gTopEigenGenes, topTFs = gTopEigenTFs))
    
}

.facetLabel = function(degrees.table) {
    
    summary = degrees.table %>%
        dplyr::group_by(class) %>%
        dplyr::summarise(mean = mean(.data$Degree), median = median(.data$Degree), max = max(.data$Degree), sd = sd(.data$Degree)) %>%
        dplyr::mutate(label = paste0(class, "\n(mean: ", round(mean, 1),
                                     ", median: ", round(median, 1), 
                                     ", max: ", round(max, 1),
                                     ", sd:", round(sd, 1), ")"))
    
    labels <- summary$label
    names(labels) <- summary$class
    
    return(labels)
}

.getPValPrefix <- function(display_pAdj, ontology) {
    pValPrefix = "raw "
    # p-adjust only available for non-GO ontologies
    if (display_pAdj && !stringr::str_starts(ontology, "GO_")) {
        pValPrefix = "adj. "
    }
    pValPrefix
}

############### GRN visualization ###############

#' Visualize a filtered eGRN in a flexible manner. 
#' 
#' This function can visualize a filtered eGRN in a very flexible manner and requires a \code{\linkS4class{GRN}} object as generated by \code{\link{build_eGRN_graph}}. 
#'
#' @template GRN 
#' @template outputFolder
#' @template basenameOutput
#' @template plotAsPDF
#' @template pdf_width
#' @template pdf_height
#' @param title \code{NULL} or Character. Default \code{NULL}. Title to be assigned to the plot.
#' @param maxEdgesToPlot Integer > 0. Default 500. Refers to the maximum number of connections to be plotted. If the network size is above this limit, nothing will be drawn. In such a case, it may help to either increase the value of this parameter or set the filtering criteria for the network to be more stringent, so that the network becomes smaller.
#' @param nCommunitiesMax Integer > 0. Default 8. Maximum number of communities that get a distinct coloring. All additional communities will be colored with the same (gray) color.
#' @param graph Character. Default \code{TF-gene}. One of: \code{TF-gene}, \code{TF-peak-gene}. Whether to plot a graph with links from TFs to peaks to gene, or the graph with the inferred TF to gene connections.
#' @param colorby Character. Default \code{type}. Either \code{type} or \code{community}. Color the vertices by either type (TF/peak/gene) or community. See \code{\link{calculateCommunitiesStats}}
#' @param layout Character. Default \code{fr}. One of \code{star}, \code{fr}, \code{sugiyama}, \code{kk}, \code{lgl}, \code{graphopt}, \code{mds}, \code{sphere}
#' @param vertice_color_TFs Named list. Default \code{list(h = 10, c = 85, l = c(25, 95))}. The list must specify the color in hcl format (hue, chroma, luminence). See the \code{colorspace} package for more details and examples
#' @param vertice_color_peaks Named list. Default \code{list(h = 135, c = 45, l = c(35, 95))}.
#' @param vertice_color_genes Named list. Default \code{list(h = 260, c = 80, l = c(30, 90))}.
#' @param vertexLabel_cex Numeric. Default \code{0.4}. Font size (multiplication factor, device-dependent)
#' @param vertexLabel_dist Numeric. Default \code{0} vertex. Distance between the label and the vertex.
#' @template forceRerun
#' @seealso \code{\link{build_eGRN_graph}}
#' @examples
#' GRN = loadExampleObject()
#' GRN = visualizeGRN(GRN, maxEdgesToPlot = 700, graph = "TF-gene", colorby = "type")
#' @return The same \code{\linkS4class{GRN}} object, without modifications.
#' @export
visualizeGRN <- function(GRN, outputFolder = NULL,  basenameOutput = NULL, plotAsPDF = TRUE, pdf_width = 12, pdf_height = 12,
                         title = NULL, maxEdgesToPlot = 500, nCommunitiesMax = 8, graph = "TF-gene" , colorby = "type", layout = "fr",
                         vertice_color_TFs = list(h = 10, c = 85, l = c(25, 95)), vertice_color_peaks = list(h = 135, c = 45, l = c(35, 95)), 
                         vertice_color_genes = list(h = 260, c = 80, l = c(30, 90)),
                         vertexLabel_cex = 0.4, vertexLabel_dist = 0, forceRerun = FALSE
) {
    
    
    start = Sys.time()
    checkmate::assertClass(GRN, "GRN")
    GRN = .addFunctionLogToObject(GRN)
    
    GRN = .makeObjectCompatible(GRN)
    
    checkmate::assertFlag(plotAsPDF)
    checkmate::assertNumeric(pdf_width, lower = 5, upper = 99)
    checkmate::assertNumeric(pdf_height, lower = 5, upper = 99)
    checkmate::assertIntegerish(maxEdgesToPlot, lower = 1)
    checkmate::assertIntegerish(nCommunitiesMax,lower = 1)
    checkmate::assertChoice(graph, c("TF-gene", "TF-peak-gene"))
    checkmate::assertChoice(colorby, c("type", "community"))
    #checkmate::assertFlag(layered)
    checkmate::assertChoice(layout, c("star", "fr", "sugiyama", "kk", "lgl", "graphopt", "mds", "sphere"))
    checkmate::assertList(vertice_color_TFs)
    checkmate::assertNames(names(vertice_color_TFs), must.include = c("h", "c", "l"), subset.of = c("h", "c", "l"))
    checkmate::assertIntegerish(unlist(vertice_color_TFs), lower = 0, upper = 360)
    checkmate::assertList(vertice_color_peaks)
    checkmate::assertNames(names(vertice_color_peaks), must.include = c("h", "c", "l"), subset.of = c("h", "c", "l"))
    checkmate::assertIntegerish(unlist(vertice_color_peaks), lower = 0, upper = 360)
    checkmate::assertList(vertice_color_genes)
    checkmate::assertNames(names(vertice_color_genes), must.include = c("h", "c", "l"), subset.of = c("h", "c", "l"))
    checkmate::assertIntegerish(unlist(vertice_color_genes), lower = 0, upper = 360)
    checkmate::assertNumeric(vertexLabel_cex)
    checkmate::assertNumeric(vertexLabel_dist)
    checkmate::assertFlag(forceRerun)
    
    
    outputFolder = .checkOutputFolder(GRN, outputFolder)
    
    if (is.null(GRN@graph$TF_gene) | is.null(GRN@graph$TF_peak_gene)) {
        message = paste0("Could not find information in the graph slot. Make sure you run the function build_eGRN_graph")
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = FALSE)
    }
    
    # Construct GRN@visualization$metadata
    GRN = .getBasic_metadata_visualization(GRN, forceRerun = forceRerun)
    # if (useDefaultMetadata) {
    #   metadata_visualization.l = .getBasic_metadata_visualization(GRN)
    #   vertice_color_TFs   = list(metadata_visualization.l[["RNA_expression_TF"]],    "ID",     "baseMean_log")
    #   vertice_color_genes = list(metadata_visualization.l[["RNA_expression_genes"]], "ENSEMBL_ID", "baseMean_log")
    #   vertice_color_peaks = list(metadata_visualization.l[["Peaks_accessibility"]],   "peakID",     "mean_log")
    # }
    # 
    #grn.merged = getGRNConnections(GRN, background = background, type = "all.filtered")
    # check that it's in sync with the @ graph
    if (graph == "TF-gene") {
        
        # For compatibility reasons with older object versions
        if (!"includeForPlotting" %in% colnames(GRN@graph$TF_gene$table)) {
            GRN@graph$TF_gene$table$includeForPlotting = TRUE
        } 
        
        grn.merged = GRN@graph$TF_gene$table %>%
            dplyr::rename(TF.ID = "V1_name")
        
        grn.merged = dplyr::filter(grn.merged, .data$includeForPlotting == TRUE)
        
        edges_final = grn.merged %>%
            dplyr::rename(from = "TF.ID", to = "V2") %>%
            dplyr::mutate(weight = 1, R = 1, linetype = "solid")
        
    } else {
        
        # For compatibility reasons with older object versions
        if (!"includeForPlotting" %in% colnames(GRN@graph$TF_peak_gene$table)) {
            GRN@graph$TF_peak_gene$table$includeForPlotting = TRUE
        } 
        
        grn.merged = GRN@graph$TF_peak_gene$table %>%
            dplyr::rename(TF.ID = "V1_name") 
        
        grn.merged = dplyr::filter(grn.merged, .data$includeForPlotting == TRUE)
        
        grn.merged$V1[!is.na(grn.merged$TF.ID)] = as.character(grn.merged$TF.ID[!is.na(grn.merged$TF.ID)]) # replace TF ensembl with TF name
        
        edges_final = grn.merged %>%
            dplyr::mutate(weight = .data$r,
                          linetype = "solid") %>%
            dplyr::rename(from = "V1", to = "V2", R =  "r") 
        
    }
    
    edges_final = edges_final %>%
        dplyr::mutate(weight_transformed = dplyr::case_when(abs(weight) < 0.2 ~ 0.2,
                                                            abs(weight) < 0.4 ~ 0.3,
                                                            abs(weight) < 0.6 ~ 0.4,
                                                            abs(weight) < 0.8 ~ 0.5,
                                                            TRUE ~ 0.6),
                      R_direction = dplyr::case_when(R < 0 ~ "neg", TRUE ~ "pos"),
                      color       = dplyr::case_when(R < 0 ~ "gray90", TRUE ~ "gray50")) %>%
        dplyr::select("from", "to", "weight", "R", "linetype", "weight_transformed", "R_direction", "color")
    
    
    
    nRows = nrow(edges_final)
    
    futile.logger::flog.info(paste0("Number of edges for the ", graph, " eGRN graph: ",nRows))
    if (maxEdgesToPlot > 500 & nRows > 500) {
        futile.logger::flog.info(paste0("Plotting many connections may need a lot of time and memory"))
    }
    
    
    
    if (plotAsPDF) {
        futile.logger::flog.info(paste0("Plotting GRN network to ", outputFolder, ifelse(is.null(basenameOutput), .getOutputFileName("plot_network"), basenameOutput),".pdf"))
        grDevices::pdf(file = paste0(outputFolder,"/", ifelse(is.null(basenameOutput), .getOutputFileName("plot_network"), basenameOutput),".pdf"), width = pdf_width, height = pdf_height )
    } else {
        futile.logger::flog.info(paste0("Plotting GRN network"))
    }
    
    if (nRows > maxEdgesToPlot) { 
        futile.logger::flog.info(paste0("Number of edges to plot (", nRows, ") exceeds limit of the maxEdgesToPlot parameter. Plotting only empty page"))
        plot(c(0, 1), c(0, 1), ann = FALSE, bty = 'n', type = 'n', xaxt = 'n', yaxt = 'n', main = title)
        message = paste0(title, "\n\nPlotting omitted.\n\nThe number of rows in the GRN (", nRows, ") exceeds the maximum of ", maxEdgesToPlot, ".\nSee the maxEdgesToPlot parameter to increase the limit")
        text(x = 0.5, y = 0.5, message, cex = 1.6, col = "red")
        
        if (plotAsPDF) {
            grDevices::dev.off()
        }
        
        .printExecutionTime(start)
        return(GRN)
    }
    
    if (nrow(grn.merged) == 0) {
        
        message = paste0("visualizeGRN: No rows left in the GRN. Creating empty plot.")
        .checkAndLogWarningsAndErrors(NULL, message, isWarning = TRUE)
        
        plot(c(0, 1), c(0, 1), ann = FALSE, bty = 'n', type = 'n', xaxt = 'n', yaxt = 'n', main = title)
        message = paste0(title, "\n\nThe GRN has no edges that pass the filter criteria.")
        text(x = 0.5, y = 0.5, message, cex = 1.6, col = "red")
        
    } else {
        
        # Fix with length
        
        if (graph == "TF-peak-gene") {
            colors_categories.l = list(
                "TF"   = RColorBrewer::brewer.pal(3,"Set1")[1], 
                "PEAK" = RColorBrewer::brewer.pal(3,"Set1")[2], 
                "GENE" = RColorBrewer::brewer.pal(3,"Set1")[3])
            
            symbols_categories.l = list(
                "TF"   = 15, # square
                "PEAK" = 21, # circle
                "GENE" = 21 # circle
            )
        }else{
            colors_categories.l = list(
                "TF"   = RColorBrewer::brewer.pal(3,"Set1")[1], 
                "GENE" = RColorBrewer::brewer.pal(3,"Set1")[3])
            
            symbols_categories.l = list(
                "TF"   = 15, # square
                "GENE" = 21 # circle
            )
        }
        
        
        nBins_orig = 100
        nBins_discard = 25
        nBins_real = nBins_orig - nBins_discard
        
        #if (!is.null(vertice_color_TFs)) {
        color_gradient = rev(colorspace::sequential_hcl(nBins_orig, h = vertice_color_TFs[["h"]], c = vertice_color_TFs[["c"]], l = vertice_color_TFs[["l"]]))[(nBins_discard + 1):nBins_orig] # red
        colors_categories.l[["TF"]]  = c(color_gradient[1], color_gradient[nBins_real]) 
        colors_categories.l[["TF"]]  = color_gradient 
        symbols_categories.l[["TF"]] = c(15,NA,15)
        vertice_color_TFs   = append(list(GRN@visualization$metadata[["RNA_expression_TF"]],    "TF.ID",     "baseMean_log"), vertice_color_TFs)
        #}
        
        if (graph == "TF-peak-gene") {
            #if (!is.null(vertice_color_peaks)) {
            color_gradient = rev(colorspace::sequential_hcl(nBins_orig, h = vertice_color_peaks[["h"]], c = vertice_color_peaks[["c"]], l = vertice_color_peaks[["l"]]))[(nBins_discard + 1):nBins_orig]  # green
            colors_categories.l[["PEAK"]] = c(color_gradient[1], color_gradient[nBins_real])
            colors_categories.l[["PEAK"]] = color_gradient
            symbols_categories.l[["PEAK"]] = c(21,NA,21)
            vertice_color_peaks = append(list(GRN@visualization$metadata[["Peaks_accessibility"]],   "peakID",     "mean_log"), vertice_color_peaks)
            # }
        }
        
        #if (!is.null(vertice_color_genes)) {
        color_gradient = rev(colorspace::sequential_hcl(nBins_orig, h = vertice_color_genes[["h"]], c = vertice_color_genes[["c"]], l = vertice_color_genes[["l"]]))[(nBins_discard + 1):nBins_orig] # blue
        colors_categories.l[["GENE"]] = c(color_gradient[1], color_gradient[nBins_real]) 
        colors_categories.l[["GENE"]] = color_gradient
        symbols_categories.l[["GENE"]] = c(21,NA,21)
        vertice_color_genes = append(list(GRN@visualization$metadata[["RNA_expression_genes"]], "ENSEMBL_ID", "baseMean_log"), vertice_color_genes)
        #}
        
        ## VERTICES ##
        
        if (graph == "TF-peak-gene") {
            shape_vertex = c("square","circle", "circle")
            names(shape_vertex) = names(colors_categories.l)
        } else {
            shape_vertex = c("square","circle")
            names(shape_vertex) = names(colors_categories.l)
        }
        
        
        
        vertices = tibble::tribble(~id,
                                   ~type,
                                   ~label,
                                   ~color_raw,
                                   ~color_bin,
                                   ~color_final)
        
        ## 1. TFs ##
        
        # Make the vertices unique, so that the same peak has only one vertice 
        # vertices_TFs = unique_TF_peak.con %>%
        #   dplyr::group_by(TF.ID) %>%
        #   dplyr::summarize(label = unique(TF.ID)) %>%
        #   dplyr::ungroup()
        
        vertices_TFs = grn.merged %>%
            dplyr::filter(grepl("^tf", .data$connectionType)) %>%
            #dplyr::rename(TF.ID = V1) %>%
            dplyr::group_by(.data$TF.ID) %>%
            dplyr::summarize(label = unique(.data$TF.ID)) %>%
            dplyr::ungroup()
        
        if (nrow(vertices_TFs) > 0) {
            
            if (!is.null(vertice_color_TFs)) {
                
                .verifyArgument_verticeType(vertice_color_TFs)
                
                vertices_TFs = vertices_TFs %>%
                    dplyr::left_join(vertice_color_TFs[[1]], by = c("TF.ID" = vertice_color_TFs[[2]])) %>%
                    dplyr::rename(color_raw = !!(vertice_color_TFs[[3]])) %>%
                    dplyr::mutate(color_bin = as.character(cut(.data$color_raw, nBins_real, labels = colors_categories.l[["TF"]], ordered_result = TRUE)))  # Transform the colors for the vertices
                
                
            } else {
                vertices_TFs = dplyr::mutate(vertices_TFs, color_raw = NA, color_bin = colors_categories.l[["TF"]])
            } 
            
            vertices = tibble::add_row(vertices, 
                                       id = vertices_TFs$TF.ID, 
                                       type = "TF", 
                                       label = as.vector(vertices_TFs$label), 
                                       color_raw = vertices_TFs$color_raw,
                                       color_bin = vertices_TFs$color_bin) 
        }
        
        
        ## 2. PEAKS ##
        
        if (graph == "TF-peak-gene") {
            
            # Make the vertices unique, so that the same peak has only one vertice 
            peaks1 = grn.merged %>% dplyr::filter(grepl("peak$", .data$connectionType)) %>% dplyr::pull(.data$V2)
            peaks2 = grn.merged %>% dplyr::filter(grepl("^peak", .data$connectionType)) %>% dplyr::pull(.data$V1)
            #vertices_peaks = tibble::tibble(peak = unique(c(unique_peak_gene.con$peak.ID, unique_TF_peak.con$peak.ID)), label = NA)
            vertices_peaks = tibble::tibble(peak = unique(c(peaks1, peaks2)), label = NA)
            
            if (nrow(vertices_peaks) > 0) {
                
                if (!is.null(vertice_color_peaks)) {
                    
                    .verifyArgument_verticeType(vertice_color_peaks)
                    
                    vertices_peaks = vertices_peaks %>%
                        dplyr::left_join(vertice_color_peaks[[1]], by = c("peak" = vertice_color_peaks[[2]])) %>%
                        dplyr::rename(color_raw = !!(vertice_color_peaks[[3]])) %>%
                        dplyr::mutate(color_bin = as.character(cut(.data$color_raw, nBins_real, labels = colors_categories.l[["PEAK"]], ordered_result = TRUE)))  # Transform the colors for the vertices
                    
                } else {
                    vertices_peaks = dplyr::mutate(vertices_peaks, color_raw = NA, color_bin = colors_categories.l[["PEAK"]])
                } 
                
                vertices = tibble::add_row(vertices, 
                                           id = vertices_peaks$peak, 
                                           type = "PEAK", 
                                           label = vertices_peaks$label, 
                                           color_raw = vertices_peaks$color_raw,
                                           color_bin = vertices_peaks$color_bin) 
            }
            
        }
        
        
        ## 3. GENES ##
        
        # Make the vertices unique, so that the same gene has only one vertice 
        # vertices_genes = unique_peak_gene.con %>%
        #   dplyr::group_by(gene.ENSEMBL) %>%
        #   dplyr::summarize(label = NA) %>% #, id2 = paste0(SYMBOL, collapse = ",")) %>%
        #   dplyr::ungroup()
        
        vertices_genes = grn.merged %>%
            dplyr::filter(grepl("gene$", .data$connectionType)) %>%
            #dplyr::rename(peak.ID = V1) %>%
            dplyr::group_by(.data$V2) %>%
            dplyr::summarize(label = NA) %>% #, id2 = paste0(SYMBOL, collapse = ",")) %>%
            dplyr::ungroup() %>%
            dplyr::rename(gene.ENSEMBL = "V2")
        
        if (nrow(vertices_genes) > 0) {
            
            if (!is.null(vertice_color_genes)) {
                
                .verifyArgument_verticeType(vertice_color_genes)
                
                vertices_genes = vertices_genes %>%
                    dplyr::left_join(vertice_color_genes[[1]], by = c("gene.ENSEMBL" = vertice_color_genes[[2]])) %>%
                    dplyr::rename(color_raw = !!(vertice_color_genes[[3]])) %>%
                    dplyr::mutate(color_bin = as.character(cut(.data$color_raw, nBins_real, labels = colors_categories.l[["GENE"]], ordered_result = TRUE)))  # Transform the colors for the vertices
                
            } else {
                vertices_genes = dplyr::mutate(vertices_genes, color_raw = NA, color_bin = colors_categories.l[["GENE"]])
            } 
            
            
            vertices = tibble::add_row(vertices, 
                                       id = vertices_genes$gene.ENSEMBL, 
                                       type = "GENE", 
                                       label = vertices_genes$label, 
                                       color_raw = vertices_genes$color_raw,
                                       color_bin = vertices_genes$color_bin) 
            
        }
        
        
        
        vertices = vertices %>%
            dplyr::mutate(size = dplyr::case_when(type == "TF" ~ 6,
                                                  type == "PEAK" ~ 3,
                                                  TRUE ~ 4),
                          size_transformed = NA) 
        
        
        vertices_colorRanges = vertices %>% dplyr::group_by(.data$type) %>% dplyr::summarize(min = min(.data$color_raw, na.rm = TRUE), max = max(.data$color_raw, na.rm = TRUE))
        
        if (nrow(dplyr::filter(vertices_colorRanges, .data$type == "GENE")) == 0) {
            vertices_colorRanges = tibble::add_row(vertices_colorRanges, type = "GENE", min = NA, max = NA)
        }
        
        if (graph == "TF-peak-gene") {
            text_categories.l = list(
                "TF"   = "TF",
                "PEAK" = "PEAK",
                "GENE" = "GENE"
            )
        }else{
            text_categories.l = list(
                "TF"   = "TF",
                "GENE" = "GENE"
            )
        }
        
        
        if (!is.null(vertice_color_TFs)) {
            subsetCur = dplyr::filter(vertices_colorRanges, .data$type == "TF") 
            text_categories.l[["TF"]] = c(signif(dplyr::pull(subsetCur, min),2), 
                                          paste0("TF expression (", vertice_color_TFs[[3]], ")"),
                                          signif(dplyr::pull(subsetCur, max),2)
            )
        }
        
        if ( graph  == "TF-peak-gene") {
            if (!is.null(vertice_color_peaks)) {
                subsetCur = dplyr::filter(vertices_colorRanges, .data$type == "PEAK") 
                text_categories.l[["PEAK"]] = c(signif(dplyr::pull(subsetCur, min),2), 
                                                paste0("Peak accessibility (", vertice_color_peaks[[3]], ")"),
                                                signif(dplyr::pull(subsetCur, max),2)
                )
            }
        }
        
        if (!is.null(vertice_color_genes)) {
            subsetCur = dplyr::filter(vertices_colorRanges, .data$type == "GENE") 
            text_categories.l[["GENE"]] = c(signif(dplyr::pull(subsetCur, min),2), 
                                            paste0("Gene expression (", vertice_color_genes[[3]], ")"),
                                            signif(dplyr::pull(subsetCur, max),2)
            )
        }
        
        
        net <- igraph::graph_from_data_frame(d = edges_final, vertices = vertices, directed = FALSE) 
        
        
        # TODO: Integrate network stats: https://kateto.net/networks-r-igraph
        # Make a separate df_to_igraph function for the entwork stats
        
        
        ########### Color and Shape parameters 
        
        # TODO: https://stackoverflow.com/questions/48490378/order-vertices-within-layers-on-tripartite-igraph
        # note: the layout_with_sugiyama which can convert the layout to tri/bipartite creates an order that minimizes edge overlap/crossover, makes it cleaner to visualize. do we want to enforce a custom order?
        
        net <- igraph::simplify(net, remove.multiple = FALSE, remove.loops = TRUE)
        deg <- igraph::degree(net, mode = "all", normalized = TRUE) # added normalized = T in case later used to determine node size. for now not rly needed
        #V(net)$size <- deg*2
        #igraph::V(net)$vertex_degree <-  deg*4 # the vertex_degree attribute doesn't need to be changed 
        igraph::V(net)$label = vertices$label
        
        
        
        #assign colors to the 5 largest communities, rest is grey
        if (colorby == "type") {
            igraph::V(net)$vertex.color = vertices$color_bin
        }else{
            
            if (is.null(GRN@graph$TF_gene$clusterGraph)) {
                GRN = calculateCommunitiesStats(GRN)
            }
            
            ncommunities = dplyr::n_distinct(GRN@graph$TF_gene$clusterGraph$membership)
            
            # If more than nCommunitiesMax are to be plotted, make them all gray ("847E89")
            if (ncommunities > nCommunitiesMax) {
                community_colors = data.frame(community = names(sort(table(GRN@graph$TF_gene$clusterGraph$membership), decreasing = TRUE)[seq_len(nCommunitiesMax)]),
                                              color = grDevices::rainbow(nCommunitiesMax))
                
                fillercolors = data.frame(community = nCommunitiesMax:ncommunities, color = "847E89") # only color the x largest communities
                community_colors = rbind(community_colors, fillercolors)
                
            }else{
                community_colors = data.frame(community = names(sort(table(GRN@graph$TF_gene$clusterGraph$membership), decreasing = TRUE)[seq_len(ncommunities)]),
                                              color = grDevices::rainbow(ncommunities))
            }
            
            TF_ensembl = GRN@graph$TF_gene$table$V1[match(vertices$id, GRN@graph$TF_gene$table$V1_name)] %>% stats::na.omit() %>% as.vector()
            gene_ensembl = GRN@graph$TF_gene$table$V2[match(vertices$id, GRN@graph$TF_gene$table$V2)] %>% stats::na.omit() %>% as.vector()
            if (graph == "TF-peak-gene") {
                network_ensembl = c(TF_ensembl, rep(NA, dplyr::n_distinct(vertices_peaks$peak)), gene_ensembl) 
            }else{
                network_ensembl = c(TF_ensembl, gene_ensembl) 
            }
            
            
            communities = GRN@graph$TF_gene$clusterGraph$membership[match(network_ensembl, GRN@graph$TF_gene$clusterGraph$names)]
            igraph::V(net)$vertex.color = community_colors$color[match(communities, community_colors$community)]
            
        }
        
        igraph::V(net)$vertex.size = vertices$size
        # https://rstudio-pubs-static.s3.amazonaws.com/337696_c6b008e0766e46bebf1401bea67f7b10.html
        # TODO: E(net)$weight <- edges_final$weight_transformed
        igraph::E(net)$color = edges_final$color
        
        #change arrow size and edge color:
        #igraph::E(net)$arrow.size <- .1
        igraph::E(net)$edge.color <- edges_final$color
        # TODO: E(net)$lty = edges_final$linetype
        # TODO: E(net)$width <- 1+E(net)$weight/12
        #igraph::E(net)$width <- 1+igraph::E(net)$weight/12
        igraph::E(net)$width <- igraph::E(net)$weight_transformed
        #igraph::E(net)$weight <- edges_final$weight_transformed # too block-y for large networks. stick to givren weight.
        
        
        if (layout == "sugiyama") {
            l <- igraph::layout_with_sugiyama(net, layers = as.numeric(as.factor(igraph::V(net)$type)), hgap = 1)$layout
            l <- cbind(l[,2], l[,1])
        }
        if (layout == "fr") {
            l <- igraph::layout_with_fr(net)
        }
        if (layout == "star") {
            l <- igraph::layout_as_star(net)
        }
        if (layout == "kk") {
            l <- igraph::layout_with_kk(net)
        }
        if (layout == "lgl") {
            l <- igraph::layout_with_lgl(net)
        }
        if (layout == "graphopt") {
            l <- igraph::layout_with_graphopt(net)
        }
        if (layout == "mds") {
            l <- igraph::layout_with_mds(net)
        } 
        if (layout == "sphere") {
            l <- igraph::layout_on_sphere(net)
        }
        
        
        
        # MyLO = matrix(0, nrow=vcount(net), ncol=2)
        # 
        # ## Horizontal position is determined by layer
        # layer <- rep(NA, length(V(net)$name))
        # layer[vertices$type == "TF"]   = 1
        # layer[vertices$type == "PEAK"] = 2
        # layer[vertices$type == "GENE"] = 3
        # MyLO[,1] = layer
        # 
        # ## Vertical position is determined by sum of sorted vertex_degree
        # for(i in 1:3) {
        #     L  = which(layer ==i)
        #     OL = order(V(net)$vertex_degree[L], decreasing= TRUE)
        #     MyLO[L[OL],2] = cumsum(V(net)$vertex_degree[L][OL])
        # }
        # 
        # layout = layout_with_sugiyama(net, layers=layer)
        # plot(net,
        #      layout=cbind(layer,layout$layout[,1]),edge.curved=0,
        #      vertex.shape=c("square","circle","square")[layer],
        #      vertex.frame.color = c("darkolivegreen","darkgoldenrod","orange3")[layer],
        #      vertex.color=c("olivedrab","goldenrod1","orange1")[layer],
        #      vertex.label.color="white",
        #      vertex.label.font=1,
        #      vertex.size =V(net)$vertex_degree,
        #      vertex.label.dist=c(0,0,0)[layer],
        #      vertex.label.degree=0)
        
        # 
        # vertex.color	 Node color
        # vertex.frame.color	 Node border color
        # vertex.shape	 One of “none”, “circle”, “square”, “csquare”, “rectangle” “crectangle”, “vrectangle”, “pie”, “raster”, or “sphere”
        # vertex.size	 Size of the node (default is 15)
        # vertex.size2	 The second size of the node (e.g. for a rectangle)
        # vertex.label	 Character vector used to label the nodes
        # vertex.label.family	 Font family of the label (e.g.“Times”, “Helvetica”)
        # vertex.label.font	 Font: 1 plain, 2 bold, 3, italic, 4 bold italic, 5 symbol
        # vertex.label.cex	 Font size (multiplication factor, device-dependent)
        # vertex.label.dist	 Distance between the label and the vertex
        # vertex.label.degree	 The position of the label in relation to the vertex, where 0 right, “pi” is left, “pi/2” is below, and “-pi/2” is above
        # EDGES	 
        # edge.color	 Edge color
        # edge.width	 Edge width, defaults to 1
        # edge.arrow.size	 Arrow size, defaults to 1
        # edge.arrow.width	 Arrow width, defaults to 1
        # edge.lty	 Line type, could be 0 or “blank”, 1 or “solid”, 2 or “dashed”, 3 or “dotted”, 4 or “dotdash”, 5 or “longdash”, 6 or “twodash”
        # edge.label	 Character vector used to label edges
        # edge.label.family	 Font family of the label (e.g.“Times”, “Helvetica”)
        # edge.label.font	 Font: 1 plain, 2 bold, 3, italic, 4 bold italic, 5 symbol
        # edge.label.cex	 Font size for edge labels
        # edge.curved	 Edge curvature, range 0-1 (FALSE sets it to 0, TRUE to 0.5)
        # arrow.mode	 Vector specifying whether edges should have arrows,
        # possible values: 0 no arrow, 1 back, 2 forward, 3 both
        
        #par(mar=c(5, 4, 4, 2) + 0.1)
        
        
        # Calling plot.new() might be necessary here
        # if (!plotAsPDF) {
        #     #plot.new()
        # }
        par(mar = c(7,0,0,0) + 0.2)
        
        plot(
            net, layout = l,
            #edge.arrow.size = 0.4, 
            # TODO: edge.arrow.width= E(net)$weight, 
            edge.font = 2,
            # TODO: edge.lty = E(net)$lty,
            vertex.size = igraph::V(net)$vertex.size,
            vertex.color = igraph::V(net)$vertex.color,
            edge.color = igraph::E(net)$color,
            edge.width = igraph::E(net)$weight_transformed,
            #edge.width = 0.5,
            vertex.label = igraph::V(net)$label,
            vertex.label.font = 1, 
            vertex.label.cex = vertexLabel_cex, 
            vertex.label.family = "Helvetica", 
            vertex.label.color = "black",
            vertex.label.degree = -pi/2,
            vertex.label = igraph::V(net)$label,
            vertex.label.dist = vertexLabel_dist,
            vertex.shape = shape_vertex[igraph::V(net)$type],
            main = title
        )
        
        
        if (colorby == "type") {
            
            text_final    = c(c(paste0(sapply(text_categories.l, '[[', 1), " (sel. min.)"), "negative (fixed color)"),   
                              c(sapply(text_categories.l, '[[', 2), "Correlation between vertices"),    
                              c(paste0(sapply(text_categories.l, '[[', 3), " (sel. max.)"), "positive (fixed color)")#, 
                              #c("Negative correlation", "Positive correlation", "bla")
            )
            symbols_final = c(c(sapply(symbols_categories.l, '[[', 1), 20),
                              c(sapply(symbols_categories.l, '[[', 2), NA), 
                              c(sapply(symbols_categories.l, '[[', 3), 20)#,
                              #c(20,20,20)
            )
            if (graph == "TF-peak-gene") {
                colors_final  = c(c(sapply(colors_categories.l , '[[', 1), "blue"), 
                                  c(rep(NA,3), NA), 
                                  c(sapply(colors_categories.l , '[[', nBins_real), "grey")#,
                                  #c("red","blue","green")
                )
            }else{
                colors_final  = c(c(sapply(colors_categories.l , '[[', 1), "blue"), 
                                  c(rep(NA,2), NA), 
                                  c(sapply(colors_categories.l , '[[', nBins_real), "grey")#,
                                  #c("red","blue","green")
                )
            }
            
            legend(x =  "bottom", text_final, 
                   pch = symbols_final,
                   col = colors_final, 
                   pt.bg = colors_final, 
                   pt.cex = 1, cex = .8, bty = "n", xpd = TRUE, ncol = 3, xjust = 0.5, yjust = 0.5, 
                   inset = c(0,-0.1)
            )  
            
        }else{
            
            text_final = community_colors$community
            symbols_final = rep(21, length(text_final))
            colors_final = community_colors$color
            
            legend(x = "bottom", title = "community",
                   legend = text_final,
                   pch = symbols_final,
                   #fill = colors_final,
                   #col = colors_final,
                   pt.bg = colors_final,
                   pt.cex = 1, cex = .8, bty = "n", xpd = TRUE,
                   ncol = length(text_final), inset = c(0,-0.1)) #divide by something?
            legend(x = "bottomright", title = "Node Type",
                   legend = c("TF", ifelse(graph == "TF-gene",  "gene", "peak/gene")),
                   pch = c(22,21),
                   pt.cex = 1, cex = .8, bty = "n",  xpd = TRUE,
                   ncol = 1, inset = c(0,-0.1))
            
        }
        
        # https://stackoverflow.com/questions/24933703/adjusting-base-graphics-legend-label-width
        #labels = c("6.4", "blaaaaaaaaaaaaaaaaaaaaaaaa", "6.4")
        
        #par(mar=c(5, 2, 2, 2) + 0.1)
        #bottom, left, top, and right.
        
    }
    
    if (plotAsPDF) {
        grDevices::dev.off()
    }
    
    .printExecutionTime(start)
    
    GRN
}



.verifyArgument_verticeType <- function(vertice_color_list) {
    
    checkmate::assertList(vertice_color_list, len = 6)
    checkmate::assertDataFrame(vertice_color_list[[1]])
    checkmate::assertSubset(c(vertice_color_list[[2]], vertice_color_list[[3]]), colnames(vertice_color_list[[1]]), empty.ok = FALSE)
}




