#' highlights the two direct descendant clades of an internal node
#'
#' Particularly useful when studying neighboring clades. Note that balances that
#' correspond to multichotomies will not be displayed.
#'
#' @title geom_balance
#' @param node selected node (balance) to highlight its two direct descendant
#' @param fill color to fill in the highlight rectangle, default to "steelblue"
#' @param color color to outline highlight rectangle and divide balance, defaults to "white"
#' @param alpha alpha (transparency) for the highlight rectangle, defaults to 0.5
#' @param extend extend xmax of the highlight rectangle by the value of extend
#' @param extendto extend xmax of the highlight rectangle to the value of extendto
#' @return ggplot2
#' @export
#' @importFrom rlang sym
#' @importFrom ggplot2 aes
#' @importFrom ggplot2 GeomRect
#' @importFrom utils packageVersion
#' @author Justin Silverman and modified by Guangchuang Yu
#' @examples 
#' library(ggtree)
#' set.seed(123)
#' tr<- rtree(15)
#' x <- ggtree(tr)
#' x + geom_balance(17)
#' 
#' @references 
#' J. Silverman, et al. *A phylogenetic transform enhances
#' analysis of compositional microbiota data*. (in preparation)   
#'    
#' For more detailed demonstration, please refer to chapter 5.2.2 of 
#' *Data Integration, Manipulation and Visualization of Phylogenetic Trees*
#' <http://yulab-smu.top/treedata-book/index.html> by Guangchuang Yu.
#' @importFrom ggiraph GeomInteractiveRect
geom_balance <- function(node, fill="steelblue", color='white', alpha=.5, extend=0, extendto=NULL) {

  data = NULL
  stat = "balance"
  position = "identity"
  show.legend = NA
  na.rm = TRUE
  inherit.aes = FALSE

  mapping <- aes(x=!!sym("x"), y=!!sym("y"), node=!!sym("node"), 
                     parent=!!sym("parent"), branch.length=!!sym("branch.length"))

  layer(
    stat=StatBalance,
    data = data,
    mapping = mapping,
    geom = GeomInteractiveRect,
    position = position,
    show.legend=show.legend,
    inherit.aes = inherit.aes,
    params = list(node=node,
                  fill=fill,
                  color=color,
                  alpha=alpha,
                  extend=extend,
                  extendto=extendto,
                  na.rm = na.rm),
    check.aes = FALSE
  )
}


##' StatBalance
##' @rdname ggtree-ggproto
##' @format NULL
##' @usage NULL
##' @importFrom ggplot2 Stat
##' @export
StatBalance <- ggproto("StatBalance", Stat,
                       compute_group = function(self, data, scales, params, node, extend, extendto) {
                           ## df <- get_balance_position_(data, node, direction)
                           df <- get_balance_position(data, node)
                           df$xmax <- df$xmax + extend
                           if (!is.null(extendto) && !is.na(extendto)) {
                               if (extendto < df$xmax) {
                                   warning("extendto is too small, keep the original xmax value...")
                               } else {
                                   df$xmax <- extendto
                               }
                           }
                           return(df)
                       },
                       required_aes = c("x", "y", "branch.length")
                       )


## get position of balance (xmin, xmax, ymin, ymax)
##
##
## @title get_balance_position
## @param data tbl_tree
## @param node selected node
## @return data.frame
## @export
get_balance_position <- function(data, node) {
    purrr::map_df(c(1, 2), get_balance_position_, data=data, node=node)
}

## direction either (1 for 'up' or 2 for 'down')
## @author Justin Silverman and modified by Guangchuang Yu
get_balance_position_ <- function(data, node, direction) {
    ## ch <- tryCatch(getChild.df(data, node), error=function(e) NULL)
    ch <- tryCatch(.child.tbl_tree(data, node)$node, error=function(e) NULL)

    if (length(ch) < 2 || is.null(ch)){
        stop('balance cannot be a tip')
    } else if (length(ch) > 2){
        stop('balance has >2 direct child nodes, can use ape::multi2di to convert to binary tree')
    }

    i <- match(node, data$node)
    #sp <- tryCatch(tidytree:::offspring.tbl_tree(data, ch[direction])$node,
    #               error=function(e) ch[direction])
    sp <- tryCatch(.offspring.tbl_tree(data, ch[direction])$node,error=function(e) ch[direction])
    if (length(sp) == 0) {
        ## sp is a tip, use itself
        sp <- ch[direction]
    }
    #sp.all <- tidytree:::offspring.tbl_tree(data, i)$node
    sp.all <- .offspring.tbl_tree(data, i)$node
    sp.df <- data[match(sp, data$node),]
    sp.all.df <- data[match(sp.all, data$node),]
    n.df <- data[i,]

    ## X direction is uniform for both children, but y is only based on range of
    ## one of the two children (direction)
    x <- sp.all.df$x
    y <- sp.df$y
    ## x.n <- n.df$x

    if ("branch.length" %in% colnames(data)) {
        xmin <- min(x)-data[i, "branch.length"]/2
    } else {
        xmin <- min(sp.df$branch)
    }
    ## xmin <- x.n
    data.frame(xmin=xmin,
               xmax = max(x),
               ymin=min(y)-0.5,
               ymax=max(y)+0.5)
}
