### Sunthud Pornprasertmanit & Terrence D. Jorgensen
### Last updated: 27 May      2020
###   DEPRECATED  24 November 2022 (after 0.5-6 was on CRAN)
###   REMOVED     next time!
### fit and rotate EFA models in lavaan


## -------------------------------------
## Exploratory Factor Analysis in lavaan
## -------------------------------------

##' Analyze Unrotated Exploratory Factor Analysis Model
##'
##' This function will analyze unrotated exploratory factor analysis model. The
##' unrotated solution can be rotated by the \code{\link{orthRotate}} and
##' \code{\link{oblqRotate}} functions.
##'
##' This function will generate a lavaan script for unrotated exploratory factor
##' analysis model such that (1) all factor loadings are estimated, (2) factor
##' variances are fixed to 1, (3) factor covariances are fixed to 0, and (4) the
##' dot products of any pairs of columns in the factor loading matrix are fixed
##' to zero (Johnson & Wichern, 2002). The reason for creating this function
##' to supplement the \code{\link{factanal}} function is that users can enjoy
##' some advanced features from the \code{lavaan} package, such as scaled
##' \eqn{\chi^2}, diagonally weighted least squares estimation for ordinal
##' indicators, or full-information maximum likelihood (FIML) to handle
##' incomplete data.
##'
##' @importFrom lavaan lavInspect parTable
##' @importFrom stats factanal
##'
##' @param data A target \code{data.frame}
##' @param nf The desired number of factors
##' @param varList Target observed variables. If not specified, all variables in
##'   \code{data} will be used (or \code{sample.cov} if \code{is.null(data)};
##'   see \code{\link[lavaan]{cfa}} for argument descriptions).
##' @param start Use starting values in the analysis from the
##'   \code{\link{factanal}} \code{function}. If \code{FALSE}, the starting values
##'   from the \code{lavaan} package will be used. \code{TRUE} is ignored with a
##'   warning if the \code{aux} argument is used.
##' @param aux The list of auxiliary variables. These variables will be included
##'   in the model by the saturated-correlates approach to account for missing
##'   information.
##' @param \dots Other arguments in the \code{\link[lavaan]{cfa}} function in
##'   the \code{lavaan} package, such as \code{ordered}, \code{se},
##'   \code{estimator}, or \code{sample.cov} and \code{sample.nobs}.
##'
##' @return A \code{lavaan} output of unrotated exploratory factor analysis
##' solution.
##'
##' @author Sunthud Pornprasertmanit (\email{psunthud@@gmail.com})
##'
##' Terrence D. Jorgensen (University of Amsterdam; \email{TJorgensen314@@gmail.com})
##'
##' @examples
##'
##' \donttest{
##'
##' unrotated <- efaUnrotate(HolzingerSwineford1939, nf = 3,
##'                          varList = paste0("x", 1:9), estimator = "mlr")
##' summary(unrotated, std = TRUE)
##' inspect(unrotated, "std")
##'
##' dat <- data.frame(HolzingerSwineford1939,
##'                   z = rnorm(nrow(HolzingerSwineford1939), 0, 1))
##' unrotated2 <- efaUnrotate(dat, nf = 2, varList = paste0("x", 1:9), aux = "z")
##' }
##'
##' @name efaUnrotate-deprecated
##' @usage
##' efaUnrotate(data = NULL, nf, varList = NULL,
##'             start = TRUE, aux = NULL, ...)
##' @seealso \code{\link{semTools-deprecated}}
##' @keywords internal
NULL


##' @rdname semTools-deprecated
##'
##' @section Support functions for exploratory factor analysis in \code{lavaan}:
##' The \code{efaUnrotate}, \code{orthRotate}, \code{oblqRotate}, and
##' \code{funRotate} functions will no longer be supported. These functions
##' allowed users to estimate EFA parameters with \code{lavaan}. Instead, users
##' can now directly use \code{lavaan}'s \code{\link[lavaan]{efa}()} function.
##' Exploratory SEM (ESEM) is also supported by lavaan using special operators
##' in \code{lavaan}'s \code{\link[lavaan]{model.syntax}}; see
##' \url{https://github.com/yrosseel/lavaan/issues/112} for details.
##'
##' @export
efaUnrotate <- function(data = NULL, nf, varList = NULL,
                        start = TRUE, aux = NULL, ...) {

  .Deprecated(msg = c("The efaUnrotate function is deprecated, and it will ",
                      "cease to be included in future versions of semTools. ",
                      "The lavaan package itself now provides EFA support. ",
                      "See help('semTools-deprecated) for details."))

  efaArgs <- list(...)
  if (is.null(data)) {
    ## check for summary statistics
    sample.cov  <- efaArgs$sample.cov
    sample.mean <- efaArgs$sample.mean
    sample.nobs <- efaArgs$sample.nobs
    sample.th   <- efaArgs$sample.th
    WLS.V       <- efaArgs$WLS.V
    NACOV       <- efaArgs$NACOV

    if (is.null(sample.cov)) stop('User must supply either raw data or ',
                                  'summary statistics to pass to lavaan().')
    if (is.null(varList)) varList <- colnames(sample.cov)

    anyOrdered <- !is.null(sample.th)
    ordNames <- efaArgs$ordered
    if (anyOrdered & (is.logical(ordNames) | is.null(ordNames))) {
      if (is.null(ordNames)) {
        message('Thresholds supplied, but not an ordered= argument. Must ',
                'assume all model variables are ordered.')
      }
      ordNames <- varList
    }

  } else {
    sample.cov  <- NULL
    sample.mean <- NULL
    sample.nobs <- NULL
    sample.th   <- NULL
    WLS.V       <- NULL
    NACOV       <- NULL

    efaArgs$data <- data
    if (is.null(varList)) varList <- colnames(data)

    anyOrdered <- checkOrdered(data, varList, ...)
    ordNames <- checkOrdered(data, varList, ..., return.names = TRUE)

    if (!is.null(efaArgs$group)) stop("Multi-group EFA is not currently supported.")
  }

  if (!is.null(aux)) {
    if (anyOrdered) {
      stop("The analysis model or the analysis data have ordered categorical",
           " variables. The auxiliary variable feature is not available for",
           " the models for categorical variables with the weighted least",
           " square approach.")
    }
    efaArgs$fixed.x <- FALSE
    efaArgs$missing <- "fiml"
    efaArgs$aux <- aux
    lavaancfa <- function(...) { cfa.auxiliary(...)}
  } else lavaancfa <- function(...) { lavaan::cfa(...)}
  nvar <- length(varList)
  facnames <- paste0("factor", 1:nf)
  loading <- outer(1:nvar, 1:nf, function(x, y) paste0("load", x, "_", y))
  syntax <- ""
  for (i in 1:nf) {
    variablesyntax <- paste(paste0(loading[,i], "*", varList), collapse = " + ")
    factorsyntax <- paste0(facnames[i], " =~ NA*", varList[1], " + ", variablesyntax, "\n")
    syntax <- paste(syntax, factorsyntax)
  }
  syntax <- paste(syntax, paste(paste0(facnames, " ~~ 1*", facnames),
                                collapse = "\n"), "\n")

  dataSupportsMeans <- length(setdiff(varList, ordNames)) && !(is.null(data) && is.null(sample.mean))
  meanstructure <- efaArgs$meanstructure
  if (is.null(meanstructure)) meanstructure <- anyOrdered #FIXME: wise default for EFA?
  stopifnot(is.logical(meanstructure))
  if (meanstructure && dataSupportsMeans) {
    syntax <- paste(syntax, paste(paste0(setdiff(varList, ordNames),
                                         " ~ 1"), collapse = "\n"), "\n")
  }

  if (nf > 1) {
    covsyntax <- outer(facnames, facnames,
                       function(x, y) paste0(x, " ~~ 0*", y, "\n"))[lower.tri(diag(nf), diag = FALSE)]
    syntax <- paste(syntax, paste(covsyntax, collapse = " "))
    for (i in 2:nf) {
      for (j in 1:(i - 1)) {
        loadconstraint <- paste(paste0(loading[,i], "*", loading[,j]), collapse=" + ")
        syntax <- paste(syntax, paste0("0 == ", loadconstraint), "\n")
      }
    }
  }
  if (start) {
    if (is.null(aux)) {
      List <- c(list(model = syntax, data = data), list(...))
      List$do.fit <- FALSE
      outtemp <- do.call(lavaancfa, List)
      covtemp <- lavInspect(outtemp, "sampstat")$cov
      partemp <- parTable(outtemp)
      err <- try(startload <- factanal(factors = nf, covmat = covtemp)$loadings[],
                 silent = TRUE)
      if (is(err, "try-error")) stop("The starting values from the factanal",
                                     " function cannot be calculated. Please",
                                     " use start = FALSE instead.")
      startval <- sqrt(diag(diag(covtemp))) %*% startload
      partemp$ustart[match(as.vector(loading), partemp$label)] <- as.vector(startval)
      partemp$est <- partemp$se <- NULL
      syntax <- partemp
    } else warning("The 'start' argument was ignored because factanal() does",
                   " not support auxiliary variables.  When using auxiliary",
                   " variables, set 'start = FALSE' ")
  } else {
    ## FIXME: optimizer can't escape loadings == 0 without starting values from
    ##        factanal() or by using theta parameterization
    ##        https://groups.google.com/d/msg/lavaan/ujkHmCVirEY/-LGut4ewAwAJ
    parameterization <- efaArgs$parameterization
    if (is.null(parameterization)) parameterization <- lavaan::lavOptions("parameterization")
    if (anyOrdered && parameterization != "theta")
      warning('If the default parameterization = "delta" returns results with ',
              'all factor loadings equal to zero, try either setting start ',
              '= TRUE or setting parameterization = "theta" instead.')
  }
  efaArgs$model <- syntax
  do.call(lavaancfa, efaArgs)
}



## -----------------
## Class and Methods
## -----------------

##' Class For Rotated Results from EFA
##'
##' This class contains the results of rotated exploratory factor analysis
##'
##'
##' @name EFA-class
##' @aliases EFA-class show,EFA-method summary,EFA-method
##' @docType class
##' @slot loading Rotated standardized factor loading matrix
##' @slot rotate Rotation matrix
##' @slot gradRotate gradient of the objective function at the rotated loadings
##' @slot convergence Convergence status
##' @slot phi: Factor correlation matrix. Will be an identity matrix if
##'  orthogonal rotation is used.
##' @slot se Standard errors of the rotated standardized factor loading matrix
##' @slot method Method of rotation
##' @slot call The command used to generate this object
##' @section Objects from the Class: Objects can be created via the
##' \code{\link{orthRotate}} or \code{\link{oblqRotate}} function.
##' @author Sunthud Pornprasertmanit (\email{psunthud@@gmail.com})
##' @seealso \code{\link{efaUnrotate}}; \code{\link{orthRotate}};
##' \code{\link{oblqRotate}}
##' @examples
##'
##' \donttest{
##'
##' unrotated <- efaUnrotate(HolzingerSwineford1939, nf = 3,
##'                          varList = paste0("x", 1:9), estimator = "mlr")
##' summary(unrotated, std = TRUE)
##' lavInspect(unrotated, "std")
##'
##' # Rotated by Quartimin
##' rotated <- oblqRotate(unrotated, method = "quartimin")
##' summary(rotated)
##' }
##'
setClass("EFA", representation(loading = "matrix",
                               rotate = "matrix",
                               gradRotate = "matrix",
                               convergence = "logical",
                               phi = "matrix",
                               se = "matrix",
                               method = "character",
                               call = "call"))

##' @rdname EFA-class
##' @aliases show,EFA-method
##' @export
setMethod("show", signature(object = "EFA"), function(object) {
	cat("Standardized Rotated Factor Loadings\n")
	print(printLoadings(object))
	cat("\nFactor Correlation\n")
	print(object@phi)
	cat("\nMethod of rotation:\t")
	cat(object@method, "\n")
	message("The standard errors are close but do not match with other packages.",
	        " Be mindful when using it.")
})

##' @rdname EFA-class
##' @aliases summary,EFA-method
##' @param object object of class \code{EFA}
##' @param suppress any standardized loadings less than the specified value
##'  will not be printed to the screen
##' @param sort \code{logical}. If \code{TRUE} (default), factor loadings will
##'  be sorted by their size in the console output
##' @export
setMethod("summary", signature(object = "EFA"),
          function(object, suppress = 0.1, sort = TRUE) {
	cat("Standardized Rotated Factor Loadings\n")
	print(printLoadings(object, suppress = suppress, sort = sort))
	cat("\nFactor Correlation\n")
	print(object@phi)
	cat("\nMethod of rotation:\t")
	cat(object@method, "\n")
	cat("\nTest Statistics for Standardized Rotated Factor Loadings\n")
	print(testLoadings(object))
})



## ------------------------------
## Rotation Constructor Functions
## ------------------------------

##' Implement orthogonal or oblique rotation
##'
##' These functions will implement orthogonal or oblique rotation on
##' standardized factor loadings from a lavaan output.
##'
##' These functions will rotate the unrotated standardized factor loadings by
##' orthogonal rotation using the \code{\link[GPArotation]{GPForth}} function or
##' oblique rotation using the \code{\link[GPArotation]{GPFoblq}} function the
##' \code{GPArotation} package. The resulting rotation matrix will be used to
##' calculate standard errors of the rotated standardized factor loading by
##' delta method by numerically computing the Jacobian matrix by the
##' \code{\link[lavaan]{lav_func_jacobian_simple}} function.
##'
## @aliases orthRotate oblqRotate funRotate
## @rdname rotate
##' @param object A lavaan output
##' @param method The method of rotations, such as \code{"varimax"},
##' \code{"quartimax"}, \code{"geomin"}, \code{"oblimin"}, or any gradient
##' projection algorithms listed in the \code{\link[GPArotation]{GPA}} function
##' in the \code{GPArotation} package.
##' @param fun The name of the function that users wish to rotate the
##' standardized solution. The functions must take the first argument as the
##' standardized loading matrix and return the \code{GPArotation} object. Check
##' this page for available functions: \code{\link[GPArotation]{rotations}}.
##' @param \dots Additional arguments for the \code{\link[GPArotation]{GPForth}}
##' function (for \code{orthRotate}), the \code{\link[GPArotation]{GPFoblq}}
##' function (for \code{oblqRotate}), or the function that users provide in the
##' \code{fun} argument.
##' @return An \code{linkS4class{EFA}} object that saves the rotated EFA solution
##' @author Sunthud Pornprasertmanit (\email{psunthud@@gmail.com})
##' @examples
##'
##' \donttest{
##'
##' unrotated <- efaUnrotate(HolzingerSwineford1939, nf = 3,
##'                          varList = paste0("x", 1:9), estimator = "mlr")
##'
##' # Orthogonal varimax
##' out.varimax <- orthRotate(unrotated, method = "varimax")
##' summary(out.varimax, sort = FALSE, suppress = 0.3)
##'
##' # Orthogonal Quartimin
##' orthRotate(unrotated, method = "quartimin")
##'
##' # Oblique Quartimin
##' oblqRotate(unrotated, method = "quartimin")
##'
##' # Geomin
##' oblqRotate(unrotated, method = "geomin")
##'
##' # Target rotation
##' library(GPArotation)
##' target <- matrix(0, 9, 3)
##' target[1:3, 1] <- NA
##' target[4:6, 2] <- NA
##' target[7:9, 3] <- NA
##' colnames(target) <- c("factor1", "factor2", "factor3")
##' ## This function works with GPArotation version 2012.3-1
##' funRotate(unrotated, fun = "targetQ", Target = target)
##' }
##'
##' @name rotate-deprecated
##'
##' @usage
##' orthRotate(object, method = "varimax", ...)
##'
##' oblqRotate(object, method = "quartimin", ...)
##'
##' funRotate(object, fun, ...)
##'
##' @seealso \code{\link{semTools-deprecated}}
##' @keywords internal
NULL


##' @rdname semTools-deprecated
##' @export
orthRotate <- function(object, method = "varimax", ...) {

  .Deprecated(msg = c("The orthRotate function is deprecated, and it will ",
                      "cease to be included in future versions of semTools. ",
                      "The lavaan package itself now provides support EFA. ",
                      "See help('semTools-deprecated) for details."))

	requireNamespace("GPArotation")
	if (!("package:GPArotation" %in% search())) attachNamespace("GPArotation")
	mc <- match.call()
	initL <- getLoad(object)
	rotated <- GPArotation::GPForth(initL, method=method, ...)
	# rotateMat <- t(solve(rotated$Th)) # defined but never used
	LIST <- seStdLoadings(rotated, object, fun = GPArotation::GPForth,
	                      MoreArgs = c(method = method, list(...)))
	# orthogonal <- rotated$orthogonal # define but never used
	loading <- rotated$loadings
	rotate <- rotated$Th
	gradRotate <- rotated$Gq
	convergence <- rotated$convergence
	method <- rotated$method
	phi <- diag(ncol(loading))
	lv.names <- colnames(loading)
	dimnames(phi) <- list(lv.names, lv.names)
	new("EFA", loading = loading, rotate = rotate, gradRotate = gradRotate,
	    convergence = convergence, phi = phi, se = LIST, method = method, call = mc)
}



##' @rdname semTools-deprecated
##' @export
oblqRotate <- function(object, method = "quartimin", ...) {

  .Deprecated(msg = c("The oblqRotate function is deprecated, and it will ",
                      "cease to be included in future versions of semTools. ",
                      "The lavaan package itself now provides support EFA. ",
                      "See help('semTools-deprecated) for details."))

  requireNamespace("GPArotation")
	if (!("package:GPArotation" %in% search())) attachNamespace("GPArotation")
	mc <- match.call()
	initL <- getLoad(object)
	rotated <- GPArotation::GPFoblq(initL, method = method, ...)
	# rotateMat <- t(solve(rotated$Th)) # defined but never used
	LIST <- seStdLoadings(rotated, object, fun = GPArotation::GPFoblq,
	                      MoreArgs = c(method = method, list(...)))
	# orthogonal <- rotated$orthogonal # defined but never used
	loading <- rotated$loadings
	rotate <- rotated$Th
	gradRotate <- rotated$Gq
	convergence <- rotated$convergence
	method <- rotated$method
	phi <- rotated$Phi
	lv.names <- colnames(loading)
	dimnames(phi) <- list(lv.names, lv.names)
	new("EFA", loading=loading, rotate = rotate, gradRotate = gradRotate,
	    convergence = convergence, phi = phi, se = LIST, method = method, call = mc)
}



##' @rdname semTools-deprecated
##' @export
funRotate <- function(object, fun, ...) {

  .Deprecated(msg = c("The funRotate function is deprecated, and it will ",
                      "cease to be included in future versions of semTools. ",
                      "The lavaan package itself now provides support EFA. ",
                      "See help('semTools-deprecated) for details."))

  stopifnot(is.character(fun))
	requireNamespace("GPArotation")
	if (!("package:GPArotation" %in% search())) attachNamespace("GPArotation")
	mc <- match.call()
	initL <- getLoad(object)
	rotated <- do.call(fun, c(list(L = initL), list(...)))
	# rotateMat <- t(solve(rotated$Th)) # defined but never used
	gradRotate <- rotated$Gq
	LIST <- seStdLoadings(rotated, object, fun = fun, MoreArgs = list(...))
	# orthogonal <- rotated$orthogonal # defined but never used
	loading <- rotated$loadings
	rotate <- rotated$Th
	convergence <- rotated$convergence
	method <- rotated$method
	phi <- rotated$Phi
	if (is.null(phi)) phi <- diag(ncol(loading))
	lv.names <- colnames(loading)
	dimnames(phi) <- list(lv.names, lv.names)
	new("EFA", loading = loading, rotate = rotate, gradRotate = gradRotate,
	    convergence = convergence, phi = phi, se = LIST, method = method, call = mc)
}



## ----------------
## Hidden Functions
## ----------------

printLoadings <- function(object, suppress = 0.1, sort = TRUE) {
  loading <- object@loading
  nf <- ncol(loading)
  loadingText <- sprintf("%.3f", object@loading)
  sig <- ifelse(testLoadings(object)$p < 0.05, "*", " ")
  loadingText <- paste0(loadingText, sig)
  loadingText[abs(loading) < suppress] <- ""
  loadingText <- matrix(loadingText, ncol = nf, dimnames = dimnames(loading))
  lead <- apply(abs(loading), 1, which.max)
  ord <- NULL
  if (sort) {
    for (i in 1:nf) {
      ord <- c(ord, intersect(order(abs(loading[,i]), decreasing = TRUE), which(lead == i)))
    }
    loadingText <- loadingText[ord,]
  }
  as.data.frame(loadingText)
}

##' @importFrom stats pnorm qnorm
testLoadings <- function(object, level = 0.95) {
  se <- object@se
  loading <- object@loading
  lv.names <- colnames(loading)
  z <- loading / se
  p <- 2 * (1 - pnorm( abs(z) ))
  crit <- qnorm(1 - (1 - level)/2)
  est <- as.vector(loading)
  se <- as.vector(se)
  warning("The standard error is currently invalid because it does not account",
          " for the variance of the rotation function. It is simply based on",
          " the delta method.")
  out <- data.frame(lhs=lv.names[col(loading)], op = "=~",
                    rhs = rownames(loading)[row(loading)], std.loading = est,
                    se = se, z = as.vector(z), p = as.vector(p),
                    ci.lower = (est - crit*se), ci.upper = (est + crit*se))
  class(out) <- c("lavaan.data.frame", "data.frame")
  out
}

##' @importFrom lavaan lavInspect
getLoad <- function(object, std = TRUE) {
	out <- lavInspect(object, "est")$lambda #FIXME: check for multiple groups
	if (std) {
		impcov <- lavaan::fitted.values(object)$cov
		impsd <- sqrt(diag(diag(impcov)))
		out <- solve(impsd) %*% out
	}
	rownames(out) <- lavaan::lavNames(object@ParTable, "ov", group = 1)
	if (!is.null(object@external$aux)) {
		out <- out[!(rownames(out) %in% object@external$aux),]
	}
	class(out) <- c("loadings", out)
	out
}

fillMult <- function(X, Y, fillrowx = 0, fillrowy = 0, fillcolx = 0, fillcoly = 0) {
	tempX <- matrix(0, nrow = nrow(X) + fillrowx, ncol = ncol(X) + fillcolx)
	tempY <- matrix(0, nrow = nrow(Y) + fillrowy, ncol = ncol(Y) + fillcoly)
	tempX[1:nrow(X), 1:ncol(X)] <- X
	tempY[1:nrow(Y), 1:ncol(Y)] <- Y
	result <- tempX %*% tempY
	result[1:nrow(X), 1:ncol(Y)]
}

##' @importFrom lavaan lavInspect
stdRotatedLoadings <- function(est, object, fun, aux = NULL, rotate = NULL, MoreArgs = NULL) {
	ov.names <- lavaan::lavNames(object@ParTable, "ov", group = 1)
  lv.names <- lavaan::lavNames(object@ParTable, "lv", group = 1)
	ind.names <- setdiff(ov.names, aux)

	# Compute model-implied covariance matrix
	partable <- object@ParTable
	# LY
	load.idx <- which(partable$op == "=~" & !(partable$rhs %in% lv.names))
	loading <- matrix(est[load.idx], ncol = length(lv.names))
	loading <- rbind(loading, matrix(0, length(aux), ncol(loading)))
	# Nu
	if (lavInspect(object, "options")$meanstructure) {
	  int.idx <- which(partable$op == "~1" & (partable$rhs == "") & (partable$lhs %in% ov.names))
	  intcept <- matrix(est[int.idx], ncol = 1)
	}
	# Theta
	th.idx <- which(partable$op == "~~" & (partable$rhs %in% ov.names) & (partable$lhs %in% ov.names))
	theta <- matrix(0, nrow = length(ov.names), ncol = length(ov.names),
	                dimnames = list(ov.names, ov.names))
	for (i in th.idx) {
		theta[partable$lhs[i], partable$rhs[i]] <- theta[partable$rhs[i], partable$lhs[i]] <- est[i]
	}
	OV <- loading %*% t(loading) + theta
	invsd <- solve(sqrt(diag(diag(OV))))

	requireNamespace("GPArotation")
	if (!("package:GPArotation" %in% search())) attachNamespace("GPArotation")
	# Compute standardized results
	loading <- invsd %*% loading

	obj <- do.call(fun, c(list(loading), MoreArgs))

	# GPArotation::GPFoblq(loading, method = "geomin")
	loading <- obj$loadings
	rotMat <- t(solve(obj$Th))

	# %*% rotate
	est[load.idx] <- as.vector(loading[seq_along(ind.names),])
	if (lavInspect(object, "options")$meanstructure) {
	  est[int.idx] <- as.vector(invsd %*% intcept)
	}
	theta <- invsd %*% theta %*% invsd
	rownames(theta) <- colnames(theta) <- ov.names
	for(i in th.idx)  est[i] <- theta[partable$lhs[i], partable$rhs[i]]

	# Put phi
	rv.idx <- which(partable$op == "~~" & partable$rhs %in% lv.names)
	templhs <- match(partable$lhs[rv.idx], lv.names)
	temprhs <- match(partable$rhs[rv.idx], lv.names)
	# rotate2 <- t(solve(rotate))
	# phi <- t(rotate2) %*% rotate2
	phi <- obj$Phi
	if (!is.null(phi)) {
		for (i in seq_along(templhs)) {
			est[rv.idx[i]] <- phi[templhs[i], temprhs[i]]
		}
	}
	est
}

##' @importFrom lavaan lavInspect parTable
seStdLoadings <- function(rotate, object, fun, MoreArgs) {
	# object <- efaUnrotate(HolzingerSwineford1939, nf=3, varList=paste0("x", 1:9), estimator="mlr")
	# initL <- getLoad(object)
	# rotate <- GPArotation::GPFoblq(initL, method="oblimin")

	rotMat <- t(solve(rotate$Th))
	gradient <- rotate$Gq
	loading <- rotate$loadings
	phi <- rotate$Phi
	if (is.null(phi)) phi <- diag(ncol(loading))

	est <- lavaan::parameterEstimates(object)$est
	aux <- object@external$aux

	# Standardized results
	JAC1 <- lavaan::lav_func_jacobian_simple(func = stdRotatedLoadings,
	                                         x = object@Fit@est, object = object,
	                                         aux = aux, rotate = rotMat,
	                                         fun = fun, MoreArgs = MoreArgs)

	LIST <- lavInspect(object, "list")
	free.idx <- which(LIST$free > 0L)
	m <- ncol(phi)
	phi.idx <- which(LIST$op == "~~" & LIST$lhs != LIST$rhs & (LIST$lhs %in% paste0("factor", 1:m)))
	JAC1 <- JAC1[c(free.idx, phi.idx), free.idx]
	VCOV <- as.matrix(lavaan::vcov(object, labels = FALSE))
	if(object@Model@eq.constraints) {
		JAC1 <- JAC1 %*% object@Model@eq.constraints.K
	}
	COV1 <- JAC1 %*% VCOV %*% t(JAC1)
	# I1 <- MASS::ginv(COV1)
	# I1p <- matrix(0, nrow(I1) + length(phi.idx), ncol(I1) + length(phi.idx))
	# I1p[1:nrow(I1), 1:ncol(I1)] <- I1
	# phi.idx2 <- nrow(I1) + 1:length(phi.idx)


	# p <- nrow(loading)
	# dconlambda <- matrix(0, m^2 - m, p*m)
	# gradphi <- gradient %*% solve(phi)
	# lambgradphi <- t(loading) %*% gradphi
	# lambphi <- loading %*% solve(phi)
	# lamblamb <- t(loading) %*% loading
	# runrow <- 1
	# descript <- NULL
	# for(u in 1:m) {
		# for(v in setdiff(1:m, u)) {
			# runcol <- 1
			# for(r in 1:m) {
				# for(i in 1:p) {
					# mir <- (1 - 1/p) * sum(loading[i,]^2) + sum(loading[,r]^2)/p - loading[i,r]^2
					# dur <- 0
					# if(u == r) dur <- 1
					# dconlambda[runrow, runcol] <- dur * gradphi[i, v] + 4 * mir * loading[i, u] * phi[r, v] + (8 - 8/p)*loading[i,r]*loading[i,u]*lambphi[i,v] + 8*loading[i,r]*lamblamb[u,r]*phi[r,v]/p - 8*loading[i,r]^2*loading[i,u]*phi[r,v]
					# descript <- rbind(descript, c(runrow, runcol, u, v, i, r))
					# runcol <- runcol + 1
				# }
			# }
			# runrow <- runrow + 1
		# }
	# }

	# dconphi <- matrix(0, m^2 - m, m*(m-1)/2)
	# runrow <- 1
	# descript2 <- NULL
	# for(u in 1:m) {
		# for(v in setdiff(1:m, u)) {
			# runcol <- 1
			# for(x in 2:m) {
				# for(y in 1:(x - 1)) {
					# dux <- 0
					# if(u == x) dux <- 1
					# duy <- 0
					# if(u == y) duy <- 1
					# dconphi[runrow, runcol] <- -(dux * phi[y, v] + duy * phi[x, v]) * lambgradphi[u, u]
					# descript2 <- rbind(descript2, c(runrow, runcol, u, v, x, y))
					# runcol <- runcol + 1
				# }
			# }
			# runrow <- runrow + 1
		# }
	# }
	# I2 <- matrix(0, nrow(I1p) + m^2 - m, ncol(I1p) + m^2 - m)
	# I2[1:nrow(I1p), 1:ncol(I1p)] <- I1p
	# I2[lamb.idx, 1:(m^2 - m) + nrow(I1p)] <- t(dconlambda)
	# I2[1:(m^2 - m) + nrow(I1p), lamb.idx] <- dconlambda
	# I2[phi.idx2, 1:(m^2 - m) + nrow(I1p)] <- t(dconphi)
	# I2[1:(m^2 - m) + nrow(I1p), phi.idx2] <- dconphi
	# COV2 <- MASS::ginv(I2)[1:nrow(I1p), 1:ncol(I1p)]

	COV2 <- COV1
	LIST <- LIST[,c("lhs", "op", "rhs", "group")]
	LIST$se <- rep(NA, length(LIST$lhs))
	LIST$se[c(free.idx, phi.idx)] <- sqrt(diag(COV2))
	tmp.se <- ifelse( LIST$se == 0.0, NA, LIST$se)
  lv.names <- lavaan::lavNames(object@ParTable, "lv", group = 1)
	partable <- parTable(object)
	idx <- which(partable$op == "=~" & !(partable$rhs %in% lv.names))
	matrix(LIST$se[idx], ncol = length(lv.names))
}

checkOrdered <- function(dat, varnames, ..., return.names = FALSE) {
  ord <- list(...)$ordered
  if (is.null(ord)) {
    ord <- FALSE
  } else {
    ord <- TRUE
  }
  if (is.null(dat)) {
    orderedVar <- FALSE
  } else {
    orderedVar <- sapply(dat[,varnames], function(x) "ordered" %in% is(x))
  }

  if (return.names) {
    ## added by TDJ 4-Feb-2020
    return(unique(c(list(...)$ordered, names(orderedVar[orderedVar]))))
  }
  any(c(ord, orderedVar))
}


