# Copyright (C) Tal Galili
#
# This file is part of dendextend.
#
# dendextend is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# dendextend is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  http://www.r-project.org/Licenses/
#





#' @title Global Comparison of two (or more) dendrograms
#' @export
#' @aliases
#' all.equal.dendlist
#' @description
#' This function makes a global comparison of two or more dendrograms trees.
#'
#' The function can get two \link{dendlist} objects and compare
#' them using \link{all.equal.list}. If a dendlist is in only "target"
#' (and not "current"), it will go through the dendlist and
#' compare all of the dendrograms within it to one another.
#'
#' @param target an object of type \link{dendrogram} or \link{dendlist}
#' @param current an object of type \link{dendrogram}
#' @param use.edge.length logical (TRUE). If to check branches' heights.
#' @param use.tip.label.order logical (FALSE). If to check labels are in the same and in identical order
#' @param use.tip.label logical (TRUE). If to check that labels are the same (regardless of order)
#' @param use.topology logical (TRUE). If to check teh existence of distinct edges
#' @param tolerance the numeric tolerance used to compare the branch lengths.
#' @param scale a positive number (NULL as default), comparison of branch height is made after scaling (i.e., dividing) them by this number.
#' @param ... Ignored.
#'
#' @seealso
#' \link{all.equal}, \link[ape]{all.equal.phylo}, \link{identical}
#' @return
#' Either TRUE (NULL for attr.all.equal) or a vector of mode "character" describing the differences
#' between target and current.
#'
#' @examples
#'
#' \dontrun{
#'
#' set.seed(23235)
#' ss <- sample(1:150, 10)
#' dend1 <- iris[ss, -5] %>%
#'   dist() %>%
#'   hclust("com") %>%
#'   as.dendrogram()
#' dend2 <- iris[ss, -5] %>%
#'   dist() %>%
#'   hclust("single") %>%
#'   as.dendrogram()
#' dend3 <- iris[ss, -5] %>%
#'   dist() %>%
#'   hclust("ave") %>%
#'   as.dendrogram()
#' dend4 <- iris[ss, -5] %>%
#'   dist() %>%
#'   hclust("centroid") %>%
#'   as.dendrogram()
#' #    cutree(dend1)
#'
#' all.equal(dend1, dend1)
#' all.equal(dend1, dend2)
#' all.equal(dend1, dend2, use.edge.length = FALSE)
#' all.equal(dend1, dend2, use.edge.length = FALSE, use.topology = FALSE)
#'
#' all.equal(dend2, dend4, use.edge.length = TRUE)
#' all.equal(dend2, dend4, use.edge.length = FALSE)
#'
#' all.equal(dendlist(dend1, dend2, dend3, dend4))
#' all.equal(dendlist(dend1, dend2, dend3, dend4), use.edge.length = FALSE)
#' all.equal(dendlist(dend1, dend1, dend1))
#' }
all.equal.dendrogram <- function(target, current,
                                 use.edge.length = TRUE,
                                 use.tip.label.order = FALSE,
                                 use.tip.label = TRUE,
                                 use.topology = TRUE,
                                 tolerance = .Machine$double.eps^0.5,
                                 scale = NULL, ...) {

  #    are_they_equal <- NULL

  # both should be dendrogram
  if (!(is.dendrogram(target))) stop("target must be a dendrogram")
  if (!(is.dendrogram(current))) {
    return("current is not a dendrogram")
  }


  if (use.edge.length) {

    #       suppressWarnings is in order to deal with the case
    # of a tree with just one item.
    # If Rcpp is on, we would get
    #  In Rcpp_get_dend_heights(tree, branches_heights = TRUE, labels_heights = FALSE) :
    #       'height' attr is missing from node, 0 is returned, please check your tree.

    target_branches_heights <- suppressWarnings(
      get_branches_heights(target)
    )
    current_branches_heights <- suppressWarnings(
      get_branches_heights(current)
    )

    result <- all.equal(target_branches_heights,
      current_branches_heights,
      tolerance = tolerance, scale = scale
    )
    if (!is.logical(result)) {
      return(paste("Difference in branch heights - ", result, collapse = ""))
    }
  }

  if (use.tip.label.order) {
    #       all.equal(c("a","b"),c("b","a"))
    #       all.equal(c("a","b"),c(1))
    result <- all.equal(labels(target), labels(current))
    if (!is.logical(result)) {
      return(paste("Difference in labels order - ", result, collapse = ""))
    }
  }

  if (use.tip.label) {
    #       all.equal(c("a","b"),c("b","a"))
    result <- all.equal(sort(labels(target)), sort(labels(current)))
    if (!is.logical(result)) {
      return(paste("Difference in labels - ", result, collapse = ""))
    }
  }

  # Check topology:
  if (use.topology) {
    target_de <- distinct_edges(target, current)
    current_de <- distinct_edges(current, target)
    if (!(is.null(target_de) & is.null(current_de))) {
      return(paste(
        "Dendrograms contain diffreent edges (i.e.: topology). Unique edges in target:",
        "|",
        paste(target_de, collapse = ", "),
        "|",
        "Unique edges in current:", paste(current_de, collapse = ", ")
      ))
    }
  }



  # It means we couldn't find any difference that matters.
  return(TRUE)
}










#' @export
all.equal.dendlist <- function(target, current, ...) {
  if (!is.dendlist(target)) stop("target needs to be a dendlist object")

  # If we have only 1 item in the dendlist - then compare the internal items only
  if (!missing(current)) {
    return(base::all.equal.list(target, current))
  }
  # else - if we ONLY have targer, then we must want to
  # compare all of the dendrograms inside it:

  n_list <- length(target)

  if (n_list < 2) {
    warning("Did you really want to compare 'all.equal' with a dendlist which contains only 1 item? (with no 'current' parameter?!)")
    return(TRUE)
  }


  are_equal <- vector("list", n_list)
  pairwise_combn <- combn(n_list, 2)

  for (i in 1:ncol(pairwise_combn)) {
    l1 <- pairwise_combn[1, i]
    l2 <- pairwise_combn[2, i]
    are_equal[[i]] <- all.equal(target[[l1]], target[[l2]], ...)
  }




  if (all(sapply(are_equal, is.logical))) {
    return(TRUE)
  } else {
    names(are_equal) <- apply(pairwise_combn, 2, paste, collapse = "==")
    are_equal <- unlist(are_equal)
    return(are_equal)
  }
}
