# 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/
#

# Creating the "dendlist" class for more easily dealing with tanglegrams and so on



#' @title Checks if the value is and empty list()
#' @description Checks if the value is and empty list(). Can be useful.
#' @param x whatever object to check
#' @return 
#' logical
#' @examples
#' # I can run this only if I'd make is_null_list exported
#' \dontrun{
#' # TRUE:
#' is_null_list(list())
#' # FALSE
#' is_null_list(list(1))
#' is_null_list(1)
#' 
#' x <- list(1, list(), 123)
#' ss_list <- sapply(x, is_null_list)
#' x <- x[!ss_list]
#' x
#' 
#' x <- list(1, list(), 123)
#' ss_list <- sapply(x, is_null_list)
#' x <- list(list())
#' x
#' }
#' 
#' \dontrun{
#' # error
#' is_null_list() 
#' }
is_null_list <- function(x) {
   identical(x, list())   
}




#' @title Creating a dendlist object from several dendrograms
#' @rdname dendlist
#' @export
#' 
#' @description
#' It accepts several dendrograms and or dendlist objects
#' and chain them all together.
#' This function aim to help with the usability of 
#' comparing two or more dendrograms.
#' 
#' @param ... several dendrogram/hclust/phylo or dendlist objects
#' If an object is hclust or phylo - it will be converted
#' into a dendrogram.
#' @param x a dendlist object
#' @param which an integer vector of length 2, indicating
#' which of the trees in the dendlist object should be plotted (relevant for dendlist)
#' 
#' When used inside dendlist, which is still an integer, but it can be of any length,
#' and it can be used to create a smaller dendlist.
#' 
#' @details
#' It there are list() in the ..., they are omitted.
#' If ... is missing, it returns an empty dendlist.
#' 
#' @return 
#' A list of class dendlist where each item
#' is a dendrogram
#' @examples
#' 
#' \dontrun{
#' 
#' dend <- iris[,-5] %>% dist %>% hclust %>% as.dendrogram
#' dend2 <- iris[,-5] %>% dist %>% hclust(method = "single") %>% as.dendrogram
#' dendlist(1:4, 5, a=dend) # Error
#' # dendlist <- function (...) list(...)
#' dendlist(dend)
#' dendlist(dend, dend)
#' dendlist(dend, dend, dendlist(dend))
#' #  notice how the order of 
#' dendlist(dend, dend2)
#' dendlist(dend) %>% dendlist(dend2)
#' dendlist(dend) %>% dendlist(dend2) %>% dendlist(dend)
#' dendlist(dend, dend2) %>% tanglegram
#' tanglegram(tree1 = dendlist(dend, dend2))
#' 
#' dend <- iris[1:20, -5] %>% dist %>% hclust %>% as.dendrogram
#' dend2 <- iris[1:20, -5] %>% dist %>% hclust(method = "single") %>% as.dendrogram
#' 
#' x <- dendlist(dend, dend2)
#' plot(x)
#' 
#' }
#' 
dendlist <- function (..., which) {
   x <- list(...)

   if(!missing(which)) {
      the_names <- names(x[[1]])[which]
   }
      
   # dendlist on a list will return a length 0 dendlist
   # First, let's remove all "list()" from the object
   ss_list <- sapply(x, is_null_list)
   x <- x[!ss_list]   
   # if we are left with nothing, return an empty dendlist
   if(identical(x, list())) {
      # warning("You entered an empty (or no) list to dendlist, a dendlist of length 0 is created.")
      x_final <- list()
      class(x_final) <- "dendlist"
      return(x_final)   
   }
   
#    object <- as.list(substitute(x))[-1L]
   n <- length(x)

   # If we have objects which are not dend or dendlist - STOP!   
   x_classes <- sapply(x, class)

   if(!all(x_classes %in% c("dendrogram", "hclust", "phylo", "dendlist"))) {
      print_x_classes <- paste(x_classes, collapse = ", ")
      stop(
         paste("Some of your object's classes are not of the type dendrogram/hclust/phylo/dendlist. Please review and fix. Their classes are:\n",
               print_x_classes))
   }
   
   # if some objects are hclust/phylo - then turn them into dend:
   x_classes_hclust_phylo <- x_classes %in% c("hclust","phylo")
   if(any(x_classes_hclust_phylo)) {
      for (i in seq_len(n)) {
         if(x_classes_hclust_phylo[i]) {
            x[[i]] <- as.dendrogram(x[[i]])
         }
      }
   }

   # If all objects are dend - just list them, and return
   x_classes_dend <- x_classes == "dendrogram"
   if(all(x_classes_dend)) {
      # skip...
      x_final <- x
      
   } else {
      n_final <- sum(x_classes == "dendrogram") +
         x[x_classes == "dendlist"] %>% sapply(length) %>% sum
      x_final <- vector("list", n_final)
      
      i_counter <- 1   
      for (i in seq_len(n)) {
         xi <- x[[i]]
         
         if(class(xi) == "dendrogram") {
            x_final[[i_counter]] <- xi   
            i_counter <- i_counter + 1
         } else {
            for(j in seq_len(length(xi))) {
               x_final[[i_counter]] <- xi[[j]]
               i_counter <- i_counter + 1
            }            
         }
         
      }
   }
   
   
   # else - everything is either c("dendrogram", "dendlist"))
   # let's make it
   
   if(!missing(which)) {
      x_final <- x_final[which]
      names(x_final) <- the_names
   }
   
   class(x_final) <- "dendlist"
   return(x_final)   
}



# @title A plot S3 method for dendlist
# @export
# @description
# If the dendlist if of length 1 - then use a normal plotting method.
# If it is of length 2 or more, use a tangelgram.
# @param x a dendlist
# @param which an integer vector of length 2, indicating
# which of the trees in the dendlist object should be plotted
# @param ... parameters to be passed to the plot/tanglegram function
# @return 
# A dendlist object
# @examples
# 
# \dontrun{
# 
# dend <- iris[1:20, -5] %>% dist %>% hclust %>% as.dendrogram
# dend2 <- iris[1:20, -5] %>% dist %>% hclust(method = "single") %>% as.dendrogram
# 
# x <- dendlist(dend, dend2)
# plot(x)
# 
# }
# 


#' @export
#' @rdname dendlist
plot.dendlist <- function(x, which = c(1L, 2L), ...) {
   if(!is.dendlist(x)) stop("x is not a dendlist")
   
   x_length <- length(x)
   if(x_length == 1) {
      plot(x[[1]], ...)
      return(x)
   }
   if(x_length >= 2) tanglegram(x[[which[1]]], x[[which[2]]], ...)      
}



#' @title Try to coerce something into a dendlist
#' @export
#' @description
#' It removes stuff that are not dendgrogram/dendlist
#' and turns what is left into a dendlist
#' @param x a list with several dendrogram/hclust/phylo or dendlist objects
#' and other junk that should be omitted.
#' @param ... NOT USED
#' @return 
#' A list of class dendlist where each item
#' is a dendrogram
#' @examples
#' 
#' \dontrun{
#' 
#' dend <- iris[,-5] %>% dist %>% hclust %>% as.dendrogram
#' dend2 <- iris[,-5] %>% dist %>% hclust(method = "single") %>% as.dendrogram
#' 
#' x = list(dend, 1, dend2)
#' as.dendlist(x)
#' 
#' }
#' 
as.dendlist <- function (x, ...) {
#    x <- list(...)
   # If we have objects which are not dend or dendlist - STOP!   
   x_classes <- sapply(x, class)
   ss <- x_classes %in% c("dendrogram", "hclust", "phylo", "dendlist")   
   # keep only the parts which are relevant
   x <- x[ss]
   x_final <- list()
   for(i in seq_len(length(x))) {
      x_final <- dendlist(x_final, x[[i]])
   }
   
   return(x_final)   
}





#' @export
head.dendlist <- function (x, ...) {
   for(i in seq_len(length(x))) {
      cat("============\n", "dend ", i, "\n", "---------\n")
      head(x[[i]], ...)
   }
}




