#' checkPipelinePackages
#' 
#' Checks whether the packages required by a pipeline and its alternative 
#' methods are available.
#'
#' @param alternatives A named list of alternative parameter values
#' @param pipDef An object of class `PipelineDefinition`.
#'
#' @return Logical.
#' @export
#' 
#' @importFrom utils installed.packages
#' @examples
#' checkPipelinePackages(list(argument1="mean"), scrna_pipeline())
checkPipelinePackages <- function(alternatives, pipDef=NULL){
  fns <- unlist(alternatives[ vapply( alternatives, FUN=is.character, 
                                      FUN.VALUE=logical(1) ) ])
  fns <- lapply(fns, FUN=function(x){
    if(exists(x) && is.function(get(x))) return(get(x))
    ""
  })
  fns <- paste(unlist(fns),collapse="\n")
  if(!is.null(pipDef)){
    fns <- paste(fns, paste(stepFn(pipDef, type="functions"), collapse="\n"), 
                 paste(stepFn(pipDef, type="evaluation"), collapse="\n"))
  }
  pkg <- gregexpr("library\\(([[:alnum:]])+\\)", fns)
  pkg <- unique(regmatches(fns, pkg)[[1]])
  pkg <- gsub("\\)","",gsub("^library\\(","",pkg))
  pkg <- gsub('"',"",pkg)
  misspkg <- setdiff(pkg, row.names(installed.packages()))
  if(length(misspkg)>0) message("The following packages appear to be missing:",
       paste(misspkg, collapse=", "))
  return(length(misspkg)==0)
}



#' parsePipNames
#' 
#' Parses the names of analyses performed through `runPipeline` to extract a 
#' data.frame of parameter values (with decent classes).
#'
#' @param x The names to parse, or a data.frame with the names to parse as 
#' row.names. All names are expected to contain the same parameters.
#' @param setRowNames Logical; whether to set original names as row.names of
#' the output data.frame (default FALSE)
#' @param addcolumns An optional data.frame of `length(x)` rows to cbind to the
#' output.
#'
#' @return A data.frame
#' 
#' @importFrom utils type.convert
#' @export
#'
#' @examples
#' my_names <- c("param1=A;param2=5","param1=B;param2=0")
#' parsePipNames(my_names)
parsePipNames <- function(x, setRowNames=FALSE, addcolumns=NULL){
  if(is.data.frame(x) || is.matrix(x)){
    if(!is.null(addcolumns)){
      addcolumns <- cbind(x,addcolumns)
    }else{
      addcolumns <- x
    }
    x <- row.names(x)
  }
  x2 <- lapply(strsplit(x,";"),FUN=function(x) x)
  if(length(unique(vapply(x2, length, numeric(1))))>1) 
    stop("The different names do not have the same number of components.")
  n <- vapply(strsplit(x2[[1]],"="),FUN=function(x) x[1], character(1))
  y <- vapply(strsplit(unlist(x2),"="),FUN=function(x) x[2], character(1))
  y <- as.data.frame(matrix(y, ncol=length(n), byrow=TRUE))
  colnames(y) <- n
  for(i in seq_len(ncol(y))) y[[i]] <- type.convert(y[[i]])
  if(setRowNames) row.names(y) <- x
  if(!is.null(addcolumns)){
    row.names(addcolumns) <- NULL
    y <- cbind(y,addcolumns)
  }
  y
}

# run function `x` on object `o`; if there is no function `x`, run `alt` passing
# `x` as second argument
.runf <- function(x, o, alt=NULL, ...){
  if(exists(x) && is.function(get(x))){
    return(get(x)(o, ...))
  }else{
    if(is.null(alt)) stop("Function '",x,"' not found in environment!")
    return(alt(o, x, ...))
  }
}


#' buildCombMatrix
#' 
#' Builds a matrix of parameter combinations from a list of alternative values.
#'
#' @param alt A named list of alternative parameter values
#' @param returnIndexMatrix Logical; whether to return a matrix of indices, 
#' rather than a data.frame of factors.
#'
#' @return a matrix or data.frame
#' @export
#'
#' @examples
#' buildCombMatrix(list(param1=LETTERS[1:3], param2=1:2))
buildCombMatrix <- function(alt, returnIndexMatrix=FALSE){
  eg <- as.matrix(expand.grid(lapply(rev(alt), FUN=seq_along)))
  eg <- eg[,seq(ncol(eg),1)]
  if(returnIndexMatrix) return(eg)
  eg <- as.data.frame(eg)
  for(f in names(alt)){
    eg[,f] <- factor(alt[[f]][eg[,f]], levels=alt[[f]])
  }
  eg
}

.checkCombMatrix <- function(eg, alt){
  if(is.null(dim(eg))) 
    stop("`eg` should be a matrix or data.frame of indices or factors")
  if(!all(names(alt) %in% colnames(eg))) 
    stop("The columns of `eg` do not correspond to the arguments.")
  eg <- eg[,names(alt)]
  if(!is.matrix(eg) || !is.numeric(eg)){
    for(f in colnames(eg)){
      if(is.character(eg[,f])) eg[,f] <- factor(eg[,f])
      if(is.factor(eg[,f])){
        if(!all(levels(eg[,f])==alt[[f]])) 
          stop("If `eg` contains factors, the levels should be identical to 
                 the values of the corresponding element of `alternatives`")
        eg[,f] <- as.numeric(eg[,f])
      }
    }
  }
  if(any(is.na(eg))) stop("Final `eg` contains missing values!")
  .sortcols(eg)
}

.sortcols <- function(x){
  xi <- x[,ncol(x)]
  for(i in seq(ncol(x)-1,1)) xi <- xi+max(xi)*x[,i]
  x[order(xi),]
}

#' getQualitativePalette
#'
#' Returns a qualitative color palette of the given size. If less than 23 colors
#'  are required, the colors are based on Paul Tol's palettes. If more, the 
#'  `randomcoloR` package is used.
#'
#' @param nbcolors A positive integer indicating the number of colors
#'
#' @return A vector of colors
#'
#' @export
#' @importFrom randomcoloR distinctColorPalette
#' @examples
#' getQualitativePalette(5)
getQualitativePalette <- function(nbcolors){
  nbcolors <- round(nbcolors)
  switch(as.character(nbcolors),
         "1"=c("#4477AA"),
         "2"=c("#4477AA", "#CC6677"),
         "3"=c("#4477AA", "#DDCC77", "#CC6677"),
         "4"=c("#4477AA", "#117733", "#DDCC77", "#CC6677"),
         "5"=c("#332288", "#88CCEE", "#117733", "#DDCC77", "#CC6677"),
         "6"=c("#332288", "#88CCEE", "#117733", "#DDCC77", "#CC6677","#AA4499"),
         "7"=c("#332288", "#88CCEE", "#44AA99", "#117733", "#DDCC77", "#CC6677",
               "#AA4499"),
         "8"=c("#332288", "#88CCEE", "#44AA99", "#117733", "#999933", "#DDCC77",
               "#CC6677","#AA4499"),
         "9"=c("#332288", "#88CCEE", "#44AA99", "#117733", "#999933", "#DDCC77",
               "#CC6677", "#882255", "#AA4499"),
         "10"=c("#332288", "#88CCEE", "#44AA99", "#117733", "#999933", 
                "#DDCC77", "#661100", "#CC6677", "#882255", "#AA4499"),
         "11"=c("#332288", "#6699CC", "#88CCEE", "#44AA99", "#117733", 
                "#999933", "#DDCC77", "#661100", "#CC6677", "#882255", 
                "#AA4499"),
         "12"=c("#332288", "#6699CC", "#88CCEE", "#44AA99", "#117733", 
                "#999933", "#DDCC77", "#661100", "#CC6677", "#AA4466", 
                "#882255", "#AA4499"),
         "13"=c("#882E72", "#B178A6", "#1965B0", "#5289C7", "#7BAFDE", 
                "#4EB265", "#90C987", "#CAE0AB", "#F7EE55", "#F6C141", 
                "#F1932D", "#E8601C", "#DC050C"),
         "14"=c("#882E72", "#B178A6", "#D6C1DE", "#1965B0", "#5289C7", 
                "#7BAFDE", "#4EB265", "#90C987", "#CAE0AB", "#F7EE55", 
                "#F6C141", "#F1932D", "#E8601C", "#DC050C"),
         "15"=c("#114477", "#4477AA", "#77AADD", "#117755", "#44AA88", 
                "#99CCBB", "#777711", "#AAAA44", "#DDDD77", "#771111", 
                "#AA4444", "#DD7777", "#771144", "#AA4477", "#DD77AA"),
         "16"=c("#114477", "#4477AA", "#77AADD", "#117755", "#44AA88", 
                "#99CCBB", "#777711", "#AAAA44", "#DDDD77", "#771111", 
                "#AA4444", "#DD7777", "#771144", "#AA4477", "#DD77AA", "black"),
         distinctColorPalette(nbcolors)
  )
}


.getTrueLabelsFromNames <- function(x){
  if(is.null(names(x))) return(NULL)
  tl <- vapply(strsplit(names(x),".",fixed=TRUE), FUN.VALUE=character(1),
               FUN=function(x) x[[1]])
  names(tl) <- names(x)
  tl
}

#' farthestPoint
#'
#' Identifies the point farthest from a line passing through by the first and 
#' last points. Used for automatization of the elbow method.
#'
#' @param y Monotonically inscreasing or decreasing values
#' @param x Optional x coordinates corresponding to `y` (defaults to seq)
#'
#' @return The value of `x` farthest from the diagonal.
#' @export
#'
#' @examples
#' y <- 2^(10:1)
#' plot(y)
#' x <- farthestPoint(y)
#' points(x,y[x],pch=16)
farthestPoint <- function(y, x=NULL){
  if(is.null(x)) x <- seq_len(length(y))
  d <- apply( cbind(x,y), 1, 
              a=c(1,y[1]), b=c(length(y),rev(y)[1]), 
              FUN=function(y, a, b){
                v1 <- a-b
                v2 <- y-a
                abs(det(cbind(v1,v2)))/sqrt(sum(v1*v1))
              })
  order(d,decreasing=TRUE)[1]
}

