#' draw an oncostrip similar to cBioportal oncoprinter output.
#'
#' @param maf an \code{\link{MAF}} object generated by \code{read.maf}
#' @param genes draw oncoprint for these genes. default NULL. Plots top 5 genes.
#' @param top how many top genes to be drawn. defaults to 5.
#' @param colors named vector of colors for each Variant_Classification.
#' @param clinicalFeatures columns names from `clinical.data` slot of \code{MAF} to be drawn in the plot. Dafault NULL.
#' @param annotationDat If MAF file was read without annotations, provide a custom \code{data.frame} with a column containing Tumor_Sample_Barcodes along with rest of columns with annotations.
#' You can specify which columns to be drawn using `clinicalFeatures` argument.
#' @param sort logical sort oncomatrix for enhanced visualization. Defaults to TRUE.
#' @param sortByAnnotation logical sort oncomatrix (samples) by provided `clinicalFeatures` Defaults to FALSE. column-sort.
#' @param annotationOrder Manually specify order for annotations. Works only for first `clinicalFeatures`. Default NULL.
#' @param removeNonMutated Logical. If \code{TRUE} removes samples with no mutations in the oncoplot for better visualization. Default TRUE.
#' @param showTumorSampleBarcodes logical to include sample names.
#' @param annotationColor list of colors to use for `clinicalFeatures`. Must be a named list. Default NULL.
#' @param fontSize font size for gene names. Default 10.
#' @param titleFontSize font size for title. Default 15
#' @param legendFontSize font size for legend. Default 12
#' @return None.
#' @seealso \code{\link{oncoplot}}
#' @examples
#' laml.maf <- system.file("extdata", "tcga_laml.maf.gz", package = "maftools")
#' laml <- read.maf(maf = laml.maf)
#' dev.new()
#' oncostrip(maf = laml, genes = c('NPM1', 'RUNX1'))
#'
#' @export


oncostrip = function(maf, genes = NULL, top = 5, colors = NULL, sort = TRUE, clinicalFeatures = NULL, annotationDat = NULL,
                     sortByAnnotation = FALSE, annotationOrder = NULL, removeNonMutated = TRUE, showTumorSampleBarcodes = FALSE, annotationColor = NULL,
                     fontSize = 10, titleFontSize = 15, legendFontSize = 12){


  totSamps = as.numeric(maf@summary[3,summary])
  tsbs = levels(getSampleSummary(x = maf)[,Tumor_Sample_Barcode])

  #if user doesnt provide a gene vector, use top 5.
  if(is.null(genes)){
    genes = getGeneSummary(x = maf)[1:top, Hugo_Symbol]
    om = createOncoMatrix(m = maf, g = genes)
    mutSamples = length(unique(unlist(genesToBarcodes(maf = maf, genes = genes, justNames = TRUE))))
    #numeric matrix and char matrix
    mat_origin = om$numericMatrix

    if(nrow(mat_origin) < top){
      mat = mat_origin
    }else{
      mat = mat_origin[1:top,]
    }
  } else{
    om = createOncoMatrix(m = maf, g = genes)
    mutSamples = length(unique(unlist(genesToBarcodes(maf = maf, genes = genes, justNames = TRUE))))
    #numeric matrix and char matrix
    mat_origin = om$numericMatrix

    #Check for any missing genes and ignore them if necessary
    if(length(genes[!genes %in% rownames(mat_origin)]) > 0){
      message('Following genes are not available in MAF:')
      print(genes[!genes %in% rownames(mat_origin)])
      message('Ignoring them.')
      genes = genes[genes %in% rownames(mat_origin)]
    }

    if(length(genes) < 2){
      stop('Provide at least 2 genes.')
    }

    mat = mat_origin[as.character(genes),, drop = FALSE]
  }


  if(ncol(mat_origin) < 2){
    stop('Cannot create oncoplot for single sample. Minimum two sample required ! ')
  }

  if(nrow(mat_origin) <2){
    stop('Minimum two genes required !')
  }

  #By default oncomatrix excludes non-mutated samples. Add rest here if user requests
  if(!removeNonMutated){
    tsb.include = matrix(data = 0, nrow = length(genes), ncol = length(tsbs[!tsbs %in% colnames(mat)]))
    colnames(tsb.include) =tsbs[!tsbs %in% colnames(mat)]
    rownames(tsb.include) = rownames(mat)
    mat = cbind(mat, tsb.include)
  }

  #Annotations
  if(!is.null(clinicalFeatures)){
    if(!is.null(annotationDat)){
      data.table::setDF(annotationDat)
      if(length(clinicalFeatures[!clinicalFeatures %in% colnames(annotationDat)]) > 0){
        message('Following columns are missing from provided annotation data. Ignoring them..')
        print(clinicalFeatures[!clinicalFeatures %in% colnames(annotationDat)])
        clinicalFeatures = clinicalFeatures[clinicalFeatures %in% colnames(annotationDat)]
        if(length(clinicalFeatures) == 0){
          stop('Zero annotaions to add! Make at-least one of the provided clinicalFeatures are present in annotationDat')
        }
      }
      annotation = annotationDat[,c('Tumor_Sample_Barcode', clinicalFeatures)]
      annotation = data.frame(row.names = annotation$Tumor_Sample_Barcode ,annotation[,clinicalFeatures, drop = FALSE])
    } else{
      annotationDat = getClinicalData(x = maf)
      if(length(clinicalFeatures[!clinicalFeatures %in% colnames(annotationDat)]) > 0){
        message('Following columns are missing from annotation slot of MAF. Ignoring them..')
        print(clinicalFeatures[!clinicalFeatures %in% colnames(annotationDat)])
        clinicalFeatures = clinicalFeatures[clinicalFeatures %in% colnames(annotationDat)]
        if(length(clinicalFeatures) == 0){
          message('Make sure at-least one of the values from provided clinicalFeatures are present in annotation slot of MAF. Here are available annotaions from MAF..')
          print(colnames(getClinicalData(maf)))
          stop('Zero annotaions to add! You can also provide custom annotations via annotationDat argument.')
        }
      }
      annotation = data.frame(row.names = annotationDat$Tumor_Sample_Barcode ,annotationDat[,clinicalFeatures, with = FALSE])
    }
  }

  if(sortByAnnotation){
    if(is.null(clinicalFeatures)){
      stop("Missing annotation data. Use argument `clinicalFeatures` to add annotations")
    }
    mat = sortByAnnotation(mat, maf, annotation, annotationOrder)
  }else{
    if(sort){
      mat = sortByMutation(numMat = mat, maf = maf)
    }
  }

  mat_origin = om$oncoMatrix

  #Do the same thing for character matrix
  if(!removeNonMutated){
    tsb.include = matrix(data = '', nrow = length(genes), ncol = length(tsbs[!tsbs %in% colnames(mat_origin)]))
    colnames(tsb.include) =tsbs[!tsbs %in% colnames(mat_origin)]
    rownames(tsb.include) = rownames(mat_origin)
    mat_origin = cbind(mat_origin, tsb.include)
  }

  mat_origin = mat_origin[rownames(mat),]
  mat_origin = mat_origin[,colnames(mat)]
  mat = mat_origin

  #New version of complexheatmap complains about '' , replacing them with random strinf xxx
  mat[mat == ''] = 'xxx'

  #By default oncomatrix excludes non-mutated samples. Add rest here if user requests
  if(!removeNonMutated){
    tsb.include = matrix(data = 'xxx', nrow = length(genes), ncol = length(tsbs[!tsbs %in% colnames(mat)]))
    colnames(tsb.include) =tsbs[!tsbs %in% colnames(mat)]
    rownames(tsb.include) = rownames(mat)
    mat = cbind(mat, tsb.include)
  }

  if(removeNonMutated){
    #mutSamples = length(unique(unlist(genesToBarcodes(maf = maf, genes = rownames(mat), justNames = TRUE))))
    altStat = paste0("Altered in ", ncol(mat), " (", round(ncol(mat)/totSamps, digits = 4)*100, "%) of ", totSamps, " samples.")
  }else{
    mutSamples = length(unique(unlist(genesToBarcodes(maf = maf, genes = rownames(mat), justNames = TRUE))))
    altStat = paste0("Altered in ", mutSamples, " (", round(mutSamples/totSamps, digits = 4)*100, "%) of ", totSamps, " samples.")
  }

  #---------------------------------------Colors and vcs-------------------------------------------------

  if(is.null(colors)){
    col = c(RColorBrewer::brewer.pal(12,name = "Paired"), RColorBrewer::brewer.pal(11,name = "Spectral")[1:3],'black', 'violet', 'royalblue')
    names(col) = names = c('Nonstop_Mutation','Frame_Shift_Del','IGR','Missense_Mutation','Silent','Nonsense_Mutation',
                           'RNA','Splice_Site','Intron','Frame_Shift_Ins','Nonstop_Mutation','In_Frame_Del','ITD','In_Frame_Ins',
                           'Translation_Start_Site',"Multi_Hit", 'Amp', 'Del')
  }else{
    col = colors
  }

  #Default background gray color.
  bg = "#CCCCCC"
  #New version of complexheatmap complains about '', will replace them with random tesx, xxx
  col = c(col, 'xxx' = bg)


  variant.classes = unique(unlist(as.list(apply(mat, 2, unique))))
  variant.classes = unique(unlist(strsplit(x = variant.classes, split = ';', fixed = TRUE)))

  variant.classes = variant.classes[!variant.classes %in% c('xxx')]

  type_col = structure(col[variant.classes], names = names(col[variant.classes]))
  type_col = type_col[!is.na(type_col)]

  type_name = structure(variant.classes, names = variant.classes)

  #variant.classes = variant.classes[!variant.classes %in% c('Amp', 'Del')]

  #Make annotation
  if(!is.null(clinicalFeatures)){
    #needed such that the annotation order matches the sample order if any type of sort is used
    if(sort || sortByAnnotation){
      annotation = annotation[colnames(mat),, drop = FALSE]
    }

    if(!is.null(annotationColor)){
      bot.anno = HeatmapAnnotation(df = annotation, col = annotationColor)
    }else{
      bot.anno = HeatmapAnnotation(annotation)
    }
  }

  #------------------------------------Helper functions to add %, rowbar and colbar----------------------------------------------------
  ##This function adds percent rate
  anno_pct = function(index) {
    n = length(index)
    #pct = apply(mat_origin[rev(index), ], 1, function(x) sum(!grepl("^\\s*$", x))/length(x)) * 100
    pct = apply(numMat[rev(index),], 1, function(x) length(x[x != 0]))/as.numeric(maf@summary[3, summary]) * 100
    pct = paste0(round(pct), "%")
    grid::pushViewport(viewport(xscale = c(0, 1), yscale = c(0.5, n + 0.5)))
    grid::grid.text(pct, x = 1, y = seq_along(index), default.units = "native",
                    just = "right", gp = grid::gpar(fontsize = fontSize))
    grid::upViewport()
  }

  ha_pct = ComplexHeatmap::HeatmapAnnotation(pct = anno_pct,
                                             width = grid::grobWidth(grid::textGrob("100%", gp = grid::gpar(fontsize = 10))), which = "row")

  ##Following two funcs add grids
  add_oncoprint = function(type, x, y, width, height) {
    grid::grid.rect(x, y, width - unit(0.5, "mm"),
                    height - grid::unit(1, "mm"), gp = grid::gpar(col = NA, fill = bg))

    for (i in 1:length(variant.classes)) {
      if (any(type %in% variant.classes[i])) {
        grid::grid.rect(x, y, width - unit(0.5, "mm"), height -
                          grid::unit(1, "mm"), gp = grid::gpar(col = NA, fill = type_col[variant.classes[i]]))
      } else if (any(type %in% 'Amp')) {
        grid::grid.rect(x, y, width - unit(0.5, "mm"), height -
                          grid::unit(1, "mm"), gp = grid::gpar(col = NA, fill = bg))
        grid::grid.rect(x, y, width - unit(0.5, "mm"), height -
                          unit(15, 'mm'), gp = grid::gpar(col = NA, fill = type_col['Amp']))
      } else if (any(type %in% 'Del')) {
        grid::grid.rect(x, y, width - unit(0.5, "mm"), height -
                          grid::unit(1, "mm"), gp = grid::gpar(col = NA, fill = bg))
        grid::grid.rect(x, y, width - unit(0.5, "mm"), height - grid::unit(15, "mm")
                        , gp = grid::gpar(col = NA, fill = type_col['Del']))
      }
    }
  }

  add_oncoprint2 = function(type, x, y, width, height) {
    for (i in 1:length(variant.classes)) {
      if (any(type %in% variant.classes[i])) {
        grid::grid.rect(x, y, width - unit(0.5, "mm"), height -
                          grid::unit(1, "mm"), gp = grid::gpar(col = NA, fill = type_col[variant.classes[i]]))
      } else if (any(type %in% 'Amp')) {
        grid::grid.rect(x, y, width - unit(0.5, "mm"), height -
                          unit(15, 'mm'), gp = grid::gpar(col = NA, fill = type_col['Amp']))
      } else if (any(type %in% 'Del')) {

        grid::grid.rect(x, y, width - unit(0.5, "mm"), height - grid::unit(15, "mm")
                        , gp = grid::gpar(col = NA, fill = type_col['Del']))
      }
    }
  }

  #This is the main cel function which is passed to ComplexHeatmap::Hetamap()
  celFun = function(j, i, x, y, width, height, fill) {
    type = mat[i, j]
    if(type != 'xxx'){
      typeList = unlist(strsplit(x = as.character(type), split = ';'))
      if(length(typeList) > 1){
        for(i in 1:length(typeList)){
          add_oncoprint2(typeList[i], x, y, width, height)
        }
      }else{
        for(i in 1:length(typeList)){
          add_oncoprint(typeList[i], x, y, width, height)
        }
      }

    }else{
      add_oncoprint(type, x, y, width, height)
    }
  }

  #----------------------------------------------------------------------------------------

  if(is.null(clinicalFeatures)){
    ht = ComplexHeatmap::Heatmap(mat, rect_gp = grid::gpar(type = "none"), cell_fun = celFun,
                                 row_names_gp = grid::gpar(fontsize = fontSize, fontface = "bold"), show_column_names = showTumorSampleBarcodes,
                                 show_heatmap_legend = FALSE, top_annotation_height = grid::unit(2, "cm"), column_title = altStat,
                                 column_title_gp = gpar(fontsize = titleFontSize, fontface = "bold"))
  }else{
    ht = ComplexHeatmap::Heatmap(mat, rect_gp = grid::gpar(type = "none"), cell_fun = celFun,
                                 row_names_gp = grid::gpar(fontsize = fontSize, fontface = "bold"), show_column_names = showTumorSampleBarcodes,
                                 show_heatmap_legend = FALSE, top_annotation_height = grid::unit(2, "cm"),
                                 bottom_annotation = bot.anno, column_title = altStat,
                                 column_title_gp = gpar(fontsize = titleFontSize, fontface = "bold"))
  }

  legend = grid::legendGrob(labels = type_name[names(type_col)],  pch = 15, gp = grid::gpar(col = type_col, fontsize = legendFontSize, fontface = "bold"), nrow = 2)

  ComplexHeatmap::draw(object = ht, newpage = FALSE, annotation_legend_side = "bottom", annotation_legend_list = list(legend))
}
