##' multi_deg
##'
##' Do differential analysis according to an expression set and group
##' information.
##'
##' @inheritParams get_deg
##' @param ids optional probe-to-symbol annotation table. If `NULL`, `exp` is
##'   treated as a gene-level matrix and no ID conversion is performed.
##' @return a deg data.frame with one element per contrast
##' @author Xiaojie Sun
##' @importFrom limma lmFit
##' @importFrom limma eBayes
##' @importFrom limma topTable
##' @importFrom limma makeContrasts
##' @importFrom limma contrasts.fit
##' @importFrom dplyr mutate
##' @importFrom dplyr inner_join
##' @export
##' @examples
##' \dontrun{
##' if(requireNamespace("Biobase",quietly = TRUE)&
##'    requireNamespace("AnnoProbe",quietly = TRUE)){
##'   gse = "GSE474"
##'   geo = geo_download(gse,destdir=tempdir())
##'   geo$exp[1:4,1:4]
##'   geo$exp=log2(geo$exp+1)
##'   group_list=ifelse(stringr::str_detect(geo$pd$title,"MObese"),
##'   "MObese",ifelse(stringr::str_detect(geo$pd$title,"NonObese"),
##'   "NonObese","Obese"))
##'   group_list=factor(group_list,levels = c("NonObese","Obese","MObese"))
##'   find_anno(geo$gpl)
##'   ids <- AnnoProbe::idmap(geo$gpl,destdir = tempdir())
##'   deg = multi_deg(geo$exp,group_list,ids,adjust = FALSE,entriz = FALSE)
##'   names(deg)
##'   head(deg[[1]])
##'   head(deg[[2]])
##'   head(deg[[3]])
##' }else{
##'   if(!requireNamespace("AnnoProbe",quietly = TRUE)) {
##'     warning("Package 'AnnoProbe' needed for this function to work.
##'          Please install it by install.packages('AnnoProbe')",call. = FALSE)
##'   }
##'   if(!requireNamespace("Biobase",quietly = TRUE)) {
##'     warning("Package 'Biobase' needed for this function to work.
##'          Please install it by BiocManager::install('Biobase')",call. = FALSE)
##'   }
##' }
##' }
##' @seealso
##' \code{\link{get_deg}};\code{\link{multi_deg_all}}

multi_deg <- function(exp,
                      group_list,
                      ids = NULL,
                      logFC_cutoff = 1,
                      pvalue_cutoff = 0.05,
                      adjust = TRUE,
                      species = "human",
                      entriz = TRUE) {
  p1 <-  all(apply(exp,2,is.numeric))
  if(!p1) stop("exp must be a numeric matrix")
  group_list <- .tinyarray_prepare_group_list_safe(group_list)
  if (is.null(group_list)) return(NULL)
  if(ncol(exp)!=length(group_list))stop("wrong group_list or exp")
  px <- levels(group_list)
  design = stats::model.matrix(~0+group_list)
  colnames(design) = px
  cmb = utils::combn(px,2,simplify = FALSE)
  contrast_names = vapply(cmb,function(v) paste0(v[2],"-",v[1]),character(1))
  contrast.matrix = limma::makeContrasts(contrasts = contrast_names, levels = design)
  colnames(contrast.matrix) = contrast_names

  fit <- limma::lmFit(exp, design)
  fit2 <- limma::contrasts.fit(fit, contrast.matrix)
  fit2 <- limma::eBayes(fit2)
  deg = lapply(seq_along(contrast_names), function(i){
    limma::topTable(fit2,coef = i,number = Inf)
  })
  names(deg) = colnames(contrast.matrix)

  if (is.null(ids)) {
    ids <- NULL
  } else {
    if(ncol(ids)!=2)stop("wrong ids parameter, it should be a data.frame with probe_id and symbol")
    colnames(ids) = c("probe_id","symbol")
    ids = stats::na.omit(ids)
    if(!is.character(ids$probe_id)) ids$probe_id = as.character(ids$probe_id)
    ids = .tinyarray_dedup_by_key(ids,"symbol")
    ids = .tinyarray_dedup_by_key(ids,"probe_id")
  }

  for(i in seq_along(deg)){
    deg[[i]]$probe_id <- rownames(deg[[i]])
    if (is.null(ids)) {
      deg[[i]]$symbol <- as.character(deg[[i]]$probe_id)
    } else {
      deg[[i]] <- dplyr::inner_join(deg[[i]],ids,by="probe_id")
    }
    if(adjust){
      k1 = (deg[[i]]$adj.P.Val < pvalue_cutoff)&(deg[[i]]$logFC < -logFC_cutoff)
      k2 = (deg[[i]]$adj.P.Val < pvalue_cutoff)&(deg[[i]]$logFC > logFC_cutoff)
    }else{
      k1 = (deg[[i]]$P.Value < pvalue_cutoff)&(deg[[i]]$logFC < -logFC_cutoff)
      k2 = (deg[[i]]$P.Value < pvalue_cutoff)&(deg[[i]]$logFC > logFC_cutoff)
    }
    change = ifelse(k1,"down",ifelse(k2,"up","stable"))
    deg[[i]]$change <- change
    if(entriz){
      org = .tinyarray_orgdb_for_species(species)
      .tinyarray_require_clusterProfiler()
      s2e <- tryCatch({
        clusterProfiler::bitr(unique(deg[[i]]$symbol), fromType = "SYMBOL",
             toType = c("ENTREZID"),
             OrgDb = org$orgdb)
      },error = function(e){666})
      if(!is.character(s2e)){
        s2e <- s2e[!duplicated(s2e$SYMBOL),]
        before_n <- nrow(deg[[i]])
        deg2 <- dplyr::inner_join(deg[[i]],s2e,by=c("symbol"="SYMBOL"))
        if (nrow(deg2) < max(1L, floor(before_n * 0.1))) {
          warning(
            "Entrez ID conversion kept fewer than 10% of rows; please check the expression row names, ids, or species.",
            call. = FALSE
          )
        } else {
          deg[[i]] <- deg2
        }
      }else{
        warning("Entrez ID conversion failed, please check the previous warning.")
      }
    }
  }
  return(deg)
}
