#-----------------------------------------------------------------------#
# Package: High-dimensional Undirected Graph Estimation                 #
# huge(): The user interface for huge.mb(), huge.glasso(), huge.ct()    #
#         and huge.tiger()                                              #
#-----------------------------------------------------------------------#

#' High-dimensional undirected graph estimation
#'
#' The main function for high-dimensional undirected graph estimation. Four graph estimation methods, including (1) Meinshausen-Buhlmann graph estimation (\code{mb}) (2) graphical lasso (\code{glasso}) (3) correlation thresholding graph estimation (\code{ct}) and (4) tuning-insensitive graph estimation (\code{tiger}), are available for data analysis.
#'
#' The graph structure is estimated by Meinshausen-Buhlmann graph estimation or the graphical lasso, and both methods can be further accelerated via the lossy screening rule by preselecting the neighborhood of each variable by correlation thresholding. We target on high-dimensional data analysis usually d >> n, and the computation is memory-optimized using the sparse matrix output. We also provide a highly computationally efficient approaches correlation thresholding graph estimation.
#'
#' @param x There are 2 options: (1) \code{x} is an \code{n} by \code{d} data matrix (2) a \code{d} by \code{d} sample covariance matrix. The program automatically identifies the input matrix by checking the symmetry. (\code{n} is the sample size and \code{d} is the dimension).
#' @param lambda An optional numeric scalar or non-empty one-dimensional
#'   numeric path. For \code{method = "mb"}, \code{"glasso"}, or
#'   \code{"tiger"}, values must be finite, strictly
#'   positive, and non-increasing; ties are allowed. For \code{method = "ct"},
#'   values must be finite and non-negative, zero is allowed, and thresholds
#'   are applied in the supplied order. Leave \code{lambda = NULL} to generate
#'   a path from \code{nlambda} and \code{lambda.min.ratio}.
#' @param nlambda The number of regularization/thresholding parameters. The default value is \code{20} for \code{method = "ct"} and \code{10} for \code{method = "mb"}, \code{"glasso"} or \code{"tiger"}.
#' @param lambda.min.ratio If \code{method = "mb"}, \code{"glasso"} or \code{"tiger"}, it is the smallest value for \code{lambda}, as a fraction of the upperbound (\code{MAX}) of the regularization/thresholding parameter which makes all estimates equal to \code{0}. The program can automatically generate \code{lambda} as a sequence of length = \code{nlambda} starting from \code{MAX} to \code{lambda.min.ratio*MAX} in log scale. If \code{method = "ct"}, it is the largest sparsity level for estimated graphs. The program can automatically generate \code{lambda} as a sequence of length = \code{nlambda}, which makes the sparsity level of the graph path increases from \code{0} to \code{lambda.min.ratio} evenly.The default value is \code{0.1} when \code{method = "mb"}, \code{"glasso"} or \code{"tiger"}, and 0.05 when \code{method = "ct"}.
#' @param method Graph estimation methods with 4 options: \code{"mb"}, \code{"ct"}, \code{"glasso"} and \code{"tiger"}. The default value is \code{"mb"}.
#' @param scr If \code{scr = TRUE}, the lossy screening rule is applied to preselect the neighborhood before the graph estimation. The default value is  \code{FALSE}. NOT applicable when \code{method = "ct"} or \code{"tiger"}.
#' @param scr.num The neighborhood size after the lossy screening rule (the number of remaining neighbors per node). ONLY applicable when \code{scr = TRUE}. The default value is \code{n-1}. An alternative value is \code{n/log(n)}. ONLY applicable when \code{scr = TRUE} and \code{method = "mb"}.
#' @param cov.output If \code{cov.output = TRUE}, the output will include a path of estimated covariance matrices. ONLY applicable when \code{method = "glasso"}. Since the estimated covariance matrices are generally not sparse, please use it with care, or it may take much memory under high-dimensional setting. The default value is \code{FALSE}.
#' @param sym Symmetrize the output graphs. If \code{sym = "and"}, the edge between node \code{i} and node \code{j} is selected ONLY when both node \code{i} and node \code{j} are selected as neighbors for each other. If \code{sym = "or"}, the edge is selected when either node \code{i} or node \code{j} is selected as the neighbor for each other. The default value is \code{"or"}. ONLY applicable when \code{method = "mb"} or \code{"tiger"}.
#' @param verbose If \code{verbose = FALSE}, tracing information printing is disabled. The default value is \code{TRUE}.
#' @param input.type How to interpret \code{x}: \code{"auto"} preserves
#'   symmetry-based detection, \code{"data"} forces an observation matrix,
#'   and \code{"covariance"} requires a square covariance or correlation
#'   matrix. Use \code{"data"} for square symmetric observation matrices.
#'   For glasso covariance input, \code{"auto"} retains the historical
#'   diagonal-sensitive default lambda scale; use \code{"covariance"} for a
#'   scale based only on off-diagonal entries. Glasso may accept an indefinite
#'   pairwise covariance estimate when regularization produces a certified
#'   positive-definite result; CT, MB, and TIGER require positive
#'   semidefiniteness.
#' @return
#' An object with S3 class \code{"huge"} is returned:
#' \item{data}{
#'   The \code{n} by \code{d} data matrix or \code{d} by \code{d} sample covariance matrix from the input
#' }
#' \item{cov.input}{
#'   An indicator of the sample covariance.
#' }
#' \item{ind.mat}{
#'   The \code{scr.num} by \code{d} matrix with each column containing the indices of the remaining neighbors of the corresponding variable after the lossy screening. ONLY applicable when \code{scr = TRUE}.
#' }
#' \item{lambda}{
#'   The sequence of regularization parameters used in mb or thresholding parameters in ct.
#' }
#' \item{sym}{
#'   The \code{sym} from the input. ONLY applicable when \code{method = "mb"} or \code{"tiger"}.
#' }
#' \item{scr}{
#'   The \code{scr} from the input. ONLY applicable when \code{method = "mb"} or \code{"glasso"}.
#' }
#' \item{path}{
#'   A list of \code{d} by \code{d} adjacency matrices of estimated graphs as a graph path corresponding to \code{lambda}.
#' }
#' \item{sparsity}{
#'   The sparsity levels of the graph path.
#' }
#' \item{icov}{
#'   A list of \code{d} by \code{d} precision matrices as an alternative graph path (numerical path) corresponding to \code{lambda}. ONLY applicable when \code{method = "glasso"} or \code{"tiger"}.
#' }
#' \item{cov}{
#'   A list of \code{d} by \code{d} estimated covariance matrices corresponding to \code{lambda}. ONLY applicable when \code{cov.output = TRUE} and \code{method = "glasso"}
#' }
#' \item{method}{
#'   The method used in the graph estimation stage.
#' }
#' \item{df}{
#'   If \code{method = "mb"} or \code{"tiger"}, it is a \code{d} by \code{nlambda} matrix. Each row contains the number of nonzero coefficients along the lasso solution path. If \code{method = "glasso"}, it is a \code{nlambda} dimensional vector containing the number of nonzero coefficients along the graph path \code{icov}.
#' }
#' \item{loglik}{
#'   A \code{nlambda} dimensional vector containing the likelihood scores along the graph path (\code{icov}). ONLY applicable when \code{method = "glasso"}. For an estimated inverse covariance Z, the program only calculates log(det(Z)) - trace(SZ) where S is the empirical covariance matrix. For the likelihood for n observations, please multiply by n/2.
#' }
#' @note This function ONLY estimates the graph path. For more information about the optimal graph selection, please refer to \code{\link{huge.select}}.\cr
#' @seealso \code{\link{huge.generator}}, \code{\link{huge.select}}, \code{\link{huge.plot}}, \code{\link{huge.roc}}, and \code{\link{huge-package}}.
#' @examples
#' #generate data
#' L = huge.generator(n = 50, d = 12, graph = "hub", g = 4)
#'
#' #graph path estimation using mb
#' out1 = huge(L$data)
#' out1
#' plot(out1)         #Not aligned
#' plot(out1, align = TRUE) #Aligned
#' huge.plot(out1$path[[3]])
#' @export
huge = function(x, lambda = NULL, nlambda = NULL, lambda.min.ratio = NULL, method = "mb", scr = NULL, scr.num = NULL, cov.output = FALSE, sym = "or", verbose = TRUE, input.type = "auto")
{

	# Each method function returns a list carrying all of its output fields;
	# adopt it wholesale rather than copying fields one by one (a historical
	# source of missed-field bugs), then attach the shared metadata.
	if(method == "ct")
		est = huge.ct(x, nlambda = nlambda, lambda.min.ratio = lambda.min.ratio, lambda = lambda, verbose = verbose, input.type = input.type)
	else if(method == "mb")
		est = huge.mb(x, lambda = lambda, nlambda = nlambda, lambda.min.ratio = lambda.min.ratio, scr = scr, scr.num = scr.num, sym = sym, verbose = verbose, input.type = input.type)
	else if(method == "glasso")
		est = huge.glasso(x, nlambda = nlambda, lambda.min.ratio = lambda.min.ratio, lambda = lambda, scr = scr, cov.output = cov.output, verbose = verbose, input.type = input.type)
	else if(method == "tiger")
		est = huge.tiger(x, lambda = lambda, nlambda = nlambda, lambda.min.ratio = lambda.min.ratio, sym = sym, verbose = verbose, input.type = input.type)
	else
		stop("method must be one of \"mb\", \"glasso\", \"ct\", \"tiger\".")

	if(method %in% c("mb", "tiger")) est$sym = sym

	est$method = method
	est$data = x

	class(est) = "huge"
	return(est)
}

#' Print function for S3 class "huge"
#'
#' Print the information about the model usage, the graph path length, graph dimension, sparsity level.
#'
#' @param x An object with S3 class \code{"huge"}.
#' @param \dots System reserved (No specific usage)
#' @seealso \code{\link{huge}}
#' @export
print.huge = function(x, ...)
{
	if(x$method == "ct")
		cat("Model: graph estimation via correlation thresholding (ct)\n")
	else if(x$method == "glasso")
		cat("Model: graphical lasso (glasso)\n")
	else if(x$method == "mb")
		cat("Model: Meinshausen & Buhlmann graph estimation (mb)\n")
	else if(x$method == "tiger")
    cat("Model: tuning-insensitive approach\n")

	if((x$method == "glasso")&&(x$scr)) cat("lossy screening: on\n")

	if(x$cov.input) cat("Input: The Covariance Matrix\n")
	if(!x$cov.input) cat("Input: The Data Matrix\n")

	cat("Path length:",length(x$lambda),"\n")
	cat("Graph dimension:",ncol(x$data),"\n")
	cat("Sparsity level:",min(x$sparsity),"----->",max(x$sparsity),"\n")
}

#' Plot function for S3 class "huge"
#'
#' Plot sparsity level information and 3 typical sparse graphs from the graph path.
#'
#' @param x An object with S3 class \code{"huge"}
#' @param align If \code{align = TRUE}, 3 plotted graphs are aligned to the same sparsity levels. The default value is \code{FALSE}.
#' @param \dots System reserved (No specific usage)
#' @seealso \code{\link{huge}}
#' @export
plot.huge = function(x, align = FALSE, ...){
	old.par = .huge_graphics_state()
	on.exit(.huge_restore_graphics_state(old.par), add = TRUE)
	if(length(x$lambda) == 1)	par(mfrow = c(1, 2), pty = "s", omi=c(0.3,0.3,0.3,0.3), mai = c(0.3,0.3,0.3,0.3))
	if(length(x$lambda) == 2)	par(mfrow = c(1, 3), pty = "s", omi=c(0.3,0.3,0.3,0.3), mai = c(0.3,0.3,0.3,0.3))
	if(length(x$lambda) >= 3)	par(mfrow = c(1, 4), pty = "s", omi=c(0.3,0.3,0.3,0.3), mai = c(0.3,0.3,0.3,0.3))

	if(length(x$lambda) <= 3)	z.final = 1:length(x$lambda)

	if(length(x$lambda) >=4){
		z.max = max(x$sparsity)
		z.min = min(x$sparsity)
		z = z.max - z.min
		z.unique = unique(c(which(x$sparsity>=(z.min + 0.03*z))[1],which(x$sparsity>=(z.min + 0.07*z))[1],which(x$sparsity>=(z.min + 0.15*z))[1]))


		if(length(z.unique) == 1){
			if(z.unique<(length(x$lambda)-1))	z.final = c(z.unique,z.unique+1,z.unique+2)
			if(z.unique==(length(x$lambda)-1)) z.final = c(z.unique-1,z.unique,z.unique+1)
			if(z.unique==length(x$lambda)) 	z.final = c(z.unique-2,z.unique-1,z.unique)
		}

		if(length(z.unique) == 2){
			if(diff(z.unique)==1){
				if(z.unique[2]<length(x$lambda)) z.final = c(z.unique,z.unique[2]+1)
				if(z.unique[2]==length(x$lambda)) z.final = c(z.unique[1]-1,z.unique)
			}
			if(diff(z.unique)>1) z.final = c(z.unique[1],z.unique[1]+1,z.unique[2])
		}

		if(length(z.unique) == 3) z.final = z.unique

	

	}
	plot(x$lambda, x$sparsity, log = "x", xlab = "Regularization Parameter", ylab = "Sparsity Level", type = "l",xlim = rev(range(x$lambda)), main = "Sparsity vs. Regularization")

	lines(x$lambda[z.final],x$sparsity[z.final],type = "p")

	if(align){
		layout.grid = layout_with_fr(graph_from_adjacency_matrix(as.matrix(x$path[[z.final[length(z.final)]]]), mode="undirected", diag=FALSE))
		for(i in z.final){
			g = graph_from_adjacency_matrix(as.matrix(x$path[[i]]), mode="undirected", diag=FALSE)
			plot(g, layout=layout.grid, edge.color='gray50',vertex.color="red", vertex.size=3, vertex.label=NA, main = paste("lambda = ",as.character(round(x$lambda[i],3)),sep = ""))
		}
	}
	if(!align){
		for(i in z.final){
			g = graph_from_adjacency_matrix(as.matrix(x$path[[i]]), mode="undirected", diag=FALSE)
			layout.grid = layout_with_fr(g)
			plot(g, layout=layout.grid, edge.color='gray50',vertex.color="red", vertex.size=3, vertex.label=NA, main = paste("lambda = ",as.character(round(x$lambda[i],3)),sep = ""))
		}
	}
	if(align) cat("Three plotted graphs are aligned according to the third graph\n")
}
