# Function for running over-representation analysis (ORA) or gene set enrichment analysis (GSEA) of GO terms using clusterProfiler.

# Input ORA:  input_genes = differentially expressed genes as a vector of Ensembl IDs
#             background_genes = vector of Ensembl IDs of all studied genes (optional)

# Input GSEA: input_genes = a named vector of fold changes of ALL genes ranked in the order of decreasing fold change,
#                           with Ensembl IDs as names

# Example: run_clusterProfiler_GO(DEG.results$ID, DEG.results.full$ID, file_name = "GOBP_enrichment").


#' Runs clusterProfiler GO enrichment function for a DEG list or for a ranked gene list.
#' @param input_genes A character vector of gene IDs (ORA) or a named,
#'                    ordered vector of fold changes of ALL genes with gene IDs as names (GSEA).
#' @param background_genes A character vector of background gene IDs. If not specified, by default uses all human genes
#'                         annotated to term domain.
#' @param file_name A character string used as a file name.
#' @param ordered_query If set to TRUE, computes GSEA-style p-values for an input ranked gene list.
#' @param ontology A vector of ontology types to use. One of the following:
#'                 GO:BP = GO Biological Process
#'                 GO:MF = GO Molecular Function
#'                 GO:CC = GO Cellular Component
#' @param OrgDb Organism annotation package, by default uses "org.Hs.eg.db".
#' @param id_type By default uses "ENSEMBL". Can be any ID type that is supported by the corresponding OrgDb.
#' @param pvalueCutoff Adjusted p-value cut-off.
#' @param min_set_size Minimum size of the functional category, uses 10 by default.
#' @param max_set_size Maximum size of the functional category, uses 1000 by default.
#' @param min_overlap Minimum size of the overlap (intersection) between query and functional category,
#'                    smaller intersections are excluded. By default uses 2.
#' @param pAdjustMethod The algorithm used for multiple testing correction, one of
#'                      "holm", "hochberg", "hommel", "bonferroni", "BH", "BY", "fdr", "none". By default uses "BH".
#' @param similarity_filtering Similarity filtering method, either FALSE (default) or TRUE (uses simplify function).
#' @param rng.seed Optional integer. If supplied, \code{set.seed()} is called with this value before
#'   the permutation-based gene set enrichment analysis, making the result reproducible. Defaults to
#'   \code{NULL}, i.e. the random number generator is left untouched, so that the function does not
#'   alter the state of the user's session. Note that this is distinct from the \code{seed} argument
#'   of \code{clusterProfiler::gseGO()}/\code{gseKEGG()}, which controls that package's own internal seeding.
#' @return A table listing statistically significant enrichment results according to threshold set in pvalueCutoff
#'        The table is also saved in xlsx format with user-specified name.
#' @examples
#' \donttest{
#' if (requireNamespace("org.Hs.eg.db", quietly = TRUE)) {
#'   genes <- c("ENSG00000141510", "ENSG00000012048", "ENSG00000139618")
#'   # wrapped in tryCatch(): the result depends on the installed annotation database
#'   go <- tryCatch(run_clusterProfiler_GO(input_genes = genes, ontology = "BP",
#'                                         OrgDb = "org.Hs.eg.db", id_type = "ENSEMBL"),
#'                  error = function(e) {
#'                    message("GO enrichment could not be run: ", conditionMessage(e))
#'                    NULL
#'                  })
#' }
#' }
#' @export



run_clusterProfiler_GO <- function(input_genes,
                                   background_genes = "",
                                   file_name = NULL,
                                   ordered_query = FALSE,
                                   id_type = "ENSEMBL",
                                   ontology = "BP",
                                   OrgDb = "org.Hs.eg.db",
                                   pvalueCutoff = 0.05,
                                   min_set_size = 10,
                                   max_set_size = 1000,
                                   min_overlap = 2,
                                   pAdjustMethod = "BH",
                                   similarity_filtering = FALSE,
                                   rng.seed = NULL) {

  # test if needed packages are installed
  if (!requireNamespace("clusterProfiler", quietly = TRUE)) {
    stop("Package ", sQuote("clusterProfiler"),
         " must be installed to run GO enrichment.", call. = FALSE)
  }
  if (!requireNamespace(OrgDb, quietly = TRUE)) {
    stop(
      paste("Package", sQuote(OrgDb), "must be installed to use this function."),
      call. = FALSE
    )
  }

  ## An empty 'background_genes' means "use the default universe" (all annotated genes).
  ## clusterProfiler expects NULL for that, not "" which would be an empty universe.
  if (is.null(background_genes) || !any(nzchar(background_genes))) background_genes <- NULL

  if (ordered_query) {
    dw_log("Running gene set enrichment analysis...", "\n")
    if (!is.null(rng.seed)) set.seed(rng.seed)
    GSE.results <- clusterProfiler::gseGO(geneList = input_genes,
                                          OrgDb = OrgDb,
                                          ont = ontology,
                                          keyType = id_type,
                                          nPerm = 1000,
                                          minGSSize = min_set_size,
                                          maxGSSize = max_set_size,
                                          pvalueCutoff = pvalueCutoff,
                                          pAdjustMethod = "BH",
                                          seed = TRUE)

    # gseGO() returns NULL when no gene sets qualify; guard against dim(NULL)
    if (!is.null(GSE.results) && nrow(as.data.frame(GSE.results)) >= 1) {

      GSE.results.df <- data.frame(GSE.results)
      GSE.results.df$Count <- sapply(GSE.results.df$core_enrichment, function(x) length(unlist(strsplit(x, split = "/"))))
      # Filter by minimum overlap and extract only interesting columns
      GSE.results.df <- GSE.results.df[GSE.results.df$Count >= min_overlap, c(1:3,5:7,11:12)]
      colnames(GSE.results.df) <- c("Term ID", "Term description", "Gene Set Size", "Normalized Enrichment Score", "P-Value",
                                  "Adjusted P-Value", "DEGs Contributing to Enrichment", "No of DEGs Contributing to Enrichment")

      # switching default gene separator of clusterProfiler into comma
      GSE.results.df$`DEGs Contributing to Enrichment` = gsub('/', ',', GSE.results.df$`DEGs Contributing to Enrichment`)

      # Write to Excel file (in comment since this is also done by wrapper)
      #cat("         Saving result into ", paste0(file_name, ".xlsx"), "...\n")
      #WriteXLS(GSE.results.df, ExcelFileName = paste0(file_name, ".xlsx"), SheetNames = NULL, BoldHeaderRow = T)

      return(GSE.results.df)

    } else {
      return("No enrichments found")

    }
  }

  if (!ordered_query) {

    dw_log("Running over-representation analysis...", "\n")
    ORA.results <- clusterProfiler::enrichGO(gene = input_genes,
                                            universe = background_genes,
                                            OrgDb = OrgDb,
                                            keyType = id_type,
                                            ont = ontology,
                                            pAdjustMethod = pAdjustMethod,
                                            pvalueCutoff  = pvalueCutoff,
                                            qvalueCutoff = 0.2,
                                            minGSSize = min_set_size,
                                            maxGSSize = max_set_size,
                                            readable = TRUE)
    # enrichGO() returns NULL when nothing maps / no gene sets qualify; guard against dim(NULL)
    if (!is.null(ORA.results) && nrow(as.data.frame(ORA.results)) >= 1) {

      if (similarity_filtering) {
        dw_log("Simplifying results...", "\n")
        ORA.results <- clusterProfiler::simplify(ORA.results)
      }

      ORA.results.df <- data.frame(ORA.results)
      # Filter by minimum overlap and extract only interesting columns

      sel.cols <- c("ID", "Description", "GeneRatio", "BgRatio", "pvalue", "p.adjust", "geneID", "Count")
      names(sel.cols) <- c("Term ID", "Term description", "Gene Ratio", "Background Ratio", "P-Value",
                           "Adjusted P-Value", "DEGs Annotated to Term", "No of DEGs Annotated to Term")
      sel.cols <- sel.cols[sel.cols %in% names(ORA.results.df)]
      ORA.results.df <- ORA.results.df[ORA.results.df$Count >= min_overlap, sel.cols]
      colnames(ORA.results.df) <- names(sel.cols)

      # switching default gene separator of clusterProfiler into comma
      ORA.results.df$`DEGs Annotated to Term` = gsub('/', ',', ORA.results.df$`DEGs Annotated to Term`)

      # Write to Excel file  (in comment since this is also done by wrapper)
      #cat("         Saving result into ", paste0(file_name, ".xlsx"), "...\n")
      #WriteXLS(ORA.results.df, ExcelFileName = paste0(file_name, ".xlsx"), SheetNames = NULL, BoldHeaderRow = T)
      return(ORA.results.df)
      }
      else {
        return("No enrichments found")
      }
  }
}







