
#' Heaviness from parent packages
#'
#' @param x An object returned by [`pkgndep()`].
#' @param rel Whether to return the absolute measure or the relative measure.
#' @param a A constant added for calculating the relative measure.
#' @param only_strong_dep Whether to only return the heaviness for strong parents.
#'
#' @details
#' The heaviness from a parent package is calculated as follows: If package B is in the `Depends`/`Imports`/`LinkingTo` fields of package A,
#' which means, package B is necessary for package A, denote `v1` as the total numbers of packages required for package A,
#' and `v2` as the total number of required packages if moving package B to `Suggests` (which means, now B is not necessary for A).
#' The absolute measure is simply `v1 -  v2` and relative measure is `(v1 + a)/(v2 + a)`. 
#'
#' In the second scenario, if B is in the `Suggests`/`Enhances` fields of package A, now `v2` is the total number of required packages if moving
#' B to `Imports`, the absolute measure is `v2 - v1` and relative measure is `(v2 + a)/(v1 + a)`.
#'
#' @export
#' @examples
#' x = readRDS(system.file("extdata", "ComplexHeatmap_dep.rds", package = "pkgndep"))
#' heaviness(x)
#' heaviness(x, rel = TRUE)
heaviness = function(x, rel = FALSE, a = 10, only_strong_dep = FALSE) {

	if(is.character(x)) {
		stop("`x` should be returned by `pkgndep()`.")
	}

	v1 = length(required_dependency_packages(x, FALSE))
	nr = nrow(x$dep_mat)
	v = numeric(nr)
	for(i in seq_len(nr)) {
		x2 = x

		if(is_field_required(x$dep_fields[i])) {
			x2$dep_fields[i] = "Suggests"
			x2$which_required[i] = FALSE
			x2$which_required_but_not_loaded[i] = FALSE
			v2 = length(required_dependency_packages(x2, FALSE))
			if(rel) {
				v[i] = (v1 + a)/(v2 + a)
			} else {
				v[i] = v1 - v2
			}
		} else {
			x2$dep_fields[i] = "Imports"
			x2$which_required[i] = TRUE
			v2 = length(required_dependency_packages(x2, FALSE))
			if(rel) {
				v[i] = (v2 + a)/(v1 + a)
			} else {
				v[i] = v2 - v1
			}
		}
	}
	names(v) = rownames(x$dep_mat)
	if(only_strong_dep) {
		v[x$which_required]
	} else {
		v
	}
}


heaviness_by_pair = function(x, i, j, rel = FALSE, a = 10, jaccard = FALSE) {
	p = required_dependency_packages(x, FALSE)
	v1 = length(p)
	v = 0
	if(x$which_required[i] & x$which_required[j]) {

		x2 = x
		x2$dep_fields[i] = "Suggests"
		x2$which_required[i] = FALSE
		x2$which_required_but_not_loaded[i] = FALSE
		p_i = setdiff(p, required_dependency_packages(x2, FALSE))

		x2 = x
		x2$dep_fields[j] = "Suggests"
		x2$which_required[j] = FALSE
		x2$which_required_but_not_loaded[j] = FALSE
		p_j = setdiff(p, required_dependency_packages(x2, FALSE))

		x2 = x
		x2$dep_fields[c(i, j)] = "Suggests"
		x2$which_required[c(i, j)] = FALSE
		x2$which_required_but_not_loaded[c(i, j)] = FALSE
		p_ij = setdiff(p, required_dependency_packages(x2, FALSE))

		p_common = setdiff(p_ij, c(p_i, p_j))
		v2 = length(p_common)

		if(jaccard) {
			if(length(p_ij) == 0) {
				v = 0
			} else {
				v = v2/length(p_ij)
			}
		} else if(rel) {
			v = (v1 + a)/((v1 - v2) + a)
		} else {
			v = v2
		}
	}
	v
}

#' Co-heaviness for pairs of parent packages
#'
#' @param x An object returned by [`pkgndep()`].
#' @param rel Whether to return the absolute measure or the relative measure.
#' @param a A constant added for calculating the relative measure.
#' @param jaccard Whether to return Jaccard coeffcient?
#'
#' @details
#' Denote a package as P and its two strong parent packages as A and B, i.e., parent packages in "Depends", "Imports" and "LinkingTo", 
#' the co-heaviness for A and B is calculated as follows.
#'
#' Denote S_A as the set of reduced dependency packages when only moving A to "Suggests" of P, and denote S_B as the set of reduced dependency
#' packages when only moving B to "Suggests" of P, denote S_AB as the set of reduced dependency packages when moving A and B together to "Suggests" of P,
#' the co-heaviness of A, B on P is calculatd as `length(setdiff(S_AB, union(S_A, S_B)))`, which is the number of reduced package only caused by co-action of A and B.
#'
#' Note the co-heaviness is only calculated for parent packages in "Depends", "Imports" and "LinkingTo".
#'
#' When `jaccard` is set to `TRUE`, the function returns jaccard coeffcient. `setdiff(S_AB, union(S_A, S_B))` is actually
#' the set of dependencies imported by and only by two parent packages A and B. Thus the jaccard coeffcient is calculated as
#' `length(setdiff(S_AB, union(S_A, S_B)))/length(S_AB)`.
#'
#' @export
#' @examples
#' \dontrun{
#' # DESeq version 1.36.0, the dependencies have been changed in later versions.
#' x = readRDS(system.file("extdata", "DESeq2_dep.rds", package = "pkgndep"))
#' hm = co_heaviness(x)
#' ComplexHeatmap::Heatmap(hm)
#' co_heaviness(x, jaccard = TRUE)
#' }
co_heaviness = function(x, rel = FALSE, a = 10, jaccard = FALSE) {

	nr = nrow(x$dep_mat)
	m = matrix(NA, nrow = nr, ncol = nr)
	rownames(m) = colnames(m) = rownames(x$dep_mat)
	diag(m) = heaviness(x, rel = rel, a = a)
	if(jaccard) {
		diag(m) = sign(diag(m))
	}

	if(nr <= 1) {
		return(m)
	}

	max_pair = NULL
	v = 0
	for(i in 1:(nr-1)) {
		for(j in (i+1):nr) {
			m[i, j] = m[j, i] = heaviness_by_pair(x, i, j, rel, a, jaccard)
			if(m[i, j] > v) {
				v = m[i, j]
				max_pair = c(i, j)
			}
		}
	}
	if(length(max_pair)) {
		max_pair = rownames(m)[max_pair]
	}
	m2 = m[x$which_required, x$which_required, drop = FALSE]
	if(nrow(m2) > 1) {
		attr(m2, "max") = max(m2[upper.tri(m2)])
		attr(m2, "max_pair") = base::sort(max_pair)
	}
	m2
}

