#' powerPrepare
#'
#' Performs some preparations common to all types of power analyses.
#'
#' @param type type of power analysis
#' @param effect effect size specifying the discrepancy between H0 and H1 (a list for multiple group models; a vector of length 2 for effect-size differences)
#' @param effect.measure type of effect, one of `"F0"`, `"RMSEA"`, `"Mc"`, `"GFI"`, `"AGFI"`
#' @param alpha alpha error
#' @param beta beta error; set either beta or power
#' @param power power (=1 - beta); set either beta or power
#' @param abratio the ratio of alpha to beta
#' @param N the number of observations (a list for multiple group models)
#' @param df the model degrees of freedom 
#' @param p the number of observed variables, required for `effect.measure = "GFI"` and `effect.measure = "AGFI"`
#' @param SigmaHat model implied covariance matrix (a list for multiple group models). Use in conjunction with `Sigma` to define `effect` and `effect.measure`. 
#' @param Sigma observed (or population) covariance matrix (a list for multiple group models). Use in conjunction with `SigmaHat` to define `effect` and `effect.measure`.
#' @param muHat model implied mean vector
#' @param mu observed (or population) mean vector
#' @param fittingFunction one of `'ML'` (default), `'WLS'`, `'DWLS'`, `'ULS'`. Defines the discrepancy function used to obtain Fmin.
#' @param simulatedPower whether to perform a simulated (`TRUE`) (rather than analytical, `FALSE`) power analysis.
#' @param modelH0 for simulated power: `lavaan` model string defining the (incorrect) analysis model.
#' @param nReplications for simulated power: number of random samples drawn.
#' @param minConvergenceRate for simulated power: the minimum convergence rate required
#' @param lavOptions for simulated power: a list of additional options passed to `lavaan`, e. g., `list(estimator = 'mlm')` to request robust ML estimation.
#' @return list
#' @importFrom utils installed.packages menu
powerPrepare <- function(type = NULL, 
                         effect = NULL, effect.measure = NULL,
                         alpha = NULL, beta = NULL, power = NULL, abratio = NULL,
                         N = NULL, df = NULL, p = NULL,
                         SigmaHat = NULL, Sigma = NULL, muHat = NULL, mu = NULL,
                         fittingFunction = 'ML',
                         simulatedPower = FALSE, 
                         modelH0 = NULL, 
                         nReplications = NULL, minConvergenceRate = NULL, lavOptions = NULL){
  
  if(!is.null(effect.measure)) effect.measure <- toupper(effect.measure)
  fittingFunction <- toupper(fittingFunction)
  
  if(!is.list(N) && length(N) > 1) N <- as.list(N)

  validateInput(type, effect = effect, effect.measure = effect.measure,
                alpha = alpha, beta = beta, power = power, abratio = abratio,
                N = N, df = df, p = p,
                SigmaHat = SigmaHat, Sigma = Sigma, muHat = muHat, mu = mu,
                fittingFunction = fittingFunction,
                simulatedPower = simulatedPower, 
                modelH0 = modelH0)

  # convert vectors to lists and vice versa
  if(is.list(N) && length(N) == 1) N <- N[[1]]
  if(is.list(effect) && length(effect) == 1) effect <- effect[[1]]
  if(is.list(SigmaHat) && length(SigmaHat) == 1) SigmaHat <- SigmaHat[[1]]
  if(is.list(Sigma) && length(Sigma) == 1) Sigma <- Sigma[[1]]
  if(is.list(muHat) && length(muHat) == 1) muHat <- muHat[[1]]
  if(is.list(mu) && length(mu) == 1) mu <- mu[[1]]
  
  
  if(!is.null(Sigma)){
    effect.measure <- 'F0'
    p <- ifelse(is.list(Sigma), ncol(Sigma[[1]]), ncol(Sigma))
  }
  
  # make sure N/effects have the same length
  if((is.list(effect) || is.list(Sigma)) && length(N) == 1){
    N <- as.list(rep(N, ifelse(is.null(Sigma), length(effect), length(Sigma))))
  }
  if(type == 'a-priori'){
    if(!is.null(Sigma) && !is.list(Sigma)){
      N <- 1 # single weight for single group model
    }else if(length(effect) == 1 || (length(effect) == 2 && is.null(N))){
      N <- 1 # single weight for single group model
    }
  }
  
  if(is.null(Sigma) && is.list(N) && length(effect) == 1){
    effect <- as.list(rep(effect, length(N)))
  }
  
  if(!is.null(effect)){
    if(is.list(effect) || length(effect) == 1){
      fmin.g <- sapply(effect, FUN = getF, effect.measure = effect.measure, df = df, p = p)
    }else{
      # power for effect differences
      f1 <- getF(effect[1], effect.measure, df[1], p)
      f2 <- getF(effect[2], effect.measure, df[2], p)
      fdiff <- abs(f2 - f1)   # let's make order arbitrary  
      fmin.g <- rep(fdiff, length(N)) 
      df <- abs(df[2] - df[1]) # let's make order arbitrary  
    }
  }
  
  if(!is.null(SigmaHat)){
    if(is.list(Sigma)){
      fmin.g <- sapply(seq_along(SigmaHat), FUN = function(x) {getF.Sigma(SigmaHat = SigmaHat[[x]], S = Sigma[[x]], muHat = muHat[[x]], mu = mu[[x]], fittingFunction = fittingFunction)})
    }else{
      fmin.g <- getF.Sigma(SigmaHat = SigmaHat, S = Sigma, muHat = muHat, mu = mu, fittingFunction = fittingFunction)
    }
  }
  
  if(!simulatedPower){
    fmin <- sum(unlist(fmin.g) * unlist(N) / sum(unlist(N)))
  }else{
    fmin <- fmin.g <- NULL
  }
  
  if(simulatedPower){
    if(!'lavaan' %in% rownames(installed.packages())) stop('This function depends on the lavaan package, so install lavaan first.')
  }
  
  list(
    effect.measure = effect.measure,
    SigmaHat = SigmaHat, 
    Sigma = Sigma, 
    muHat = muHat, 
    mu = mu,    
    N = N,
    p = p,
    effect = effect,
    fmin = fmin,
    fmin.g = fmin.g,
    df = df
  )
}

#' validateInput
#'
#' Validates input for power functions.
#'
#' @param power.type type of power analyses, one of `"a-priori"`, `"post-hoc"`, `"compromise"`, `"powerplot.byN"`, `"powerplot.byEffect"`
#' @param effect effect size specifying the discrepancy between H0 and H1
#' @param effect.measure type of effect, one of `"F0"`, `"RMSEA"`, `"Mc"`, `"GFI"`, `"AGFI"`
#' @param alpha alpha error
#' @param beta beta error
#' @param power power (= 1 - beta)
#' @param abratio ratio alpha/beta
#' @param N the number of observations
#' @param df the model degrees of freedom
#' @param p the number of observed variables, required for `effect.measure = "GFI"` and `effect.measure = "AGFI"`
#' @param SigmaHat model implied covariance matrix
#' @param Sigma observed (or population) covariance matrix
#' @param muHat model implied mean vector
#' @param mu observed (or population) mean vector
#' @param fittingFunction whether to use `ML` (the default) or `WLS`
#' @param simulatedPower whether to perform a simulated (`TRUE`) (rather than analytical, `FALSE`) power analysis.
#' @param modelH0 for simulated power: `lavaan` model string defining the (incorrect) analysis model.
#' @param power.min for plotting: minimum power
#' @param power.max for plotting: maximum power
#' @param effect.min for plotting: minimum effect
#' @param effect.max for plotting: maximum effect
#' @param steps for plotting: number of sampled points
#' @param linewidth for plotting: linewidth
validateInput <- function(power.type = NULL, effect = NULL, effect.measure = NULL,
                          alpha = NULL, beta = NULL, power = NULL, abratio = NULL,
                          N = NULL, df = NULL, p = NULL,
                          SigmaHat = NULL, Sigma = NULL, muHat = NULL, mu = NULL,
                          fittingFunction = 'ML',
                          simulatedPower = FALSE, modelH0 = NULL,
                          power.min = alpha, power.max = .999,
                          effect.min = NULL, effect.max = NULL,
                          steps = 50, linewidth = 1){

  knownEffectMeasures <- c("F0", "RMSEA", "MC", "GFI", "AGFI")
  knownFittingFunctions <- c("ML", "WLS", "DWLS", "ULS")
  
  if(!fittingFunction %in% knownFittingFunctions){
    stop(paste("Fitting function must be one of", paste(knownFittingFunctions, collapse = ', '), '.'))
  }
  
  if(!simulatedPower){
    
    if(is.null(df)) stop("df must be defined")
    lapply(df, checkPositive, message = 'df')
    
    # generic power analyses
    if(is.null(SigmaHat) && is.null(Sigma)){
      
      if(!effect.measure %in% knownEffectMeasures){
        stop(paste("Effect measure is unknown, must be one of", paste(knownEffectMeasures, collapse = ", ")))
      }
      
      # for effect-size differences, check matching length
      if(!is.null(effect) && !is.list(effect)){
        if(length(effect) > 2){
          stop("Power analyses with multiple groups requires specification of the effect size in each group provided as a list.")
        }else{
          if(length(effect) == 2 && length(df) != 2){
            stop("Power analyses for effect size differences requires specification of the df of the model pairs.")
          }
        }
      }
      
      # for multiple group analyses, check matching length
      if(is.list(N)){
        if(is.list(effect) && length(effect) == 1){
          warning("Only single effect size provided in multiple group power analyses, assuming equal effects in each group.")
        }else if(is.list(effect) && length(N) != length(effect)){
          stop("Power analyses with multiple groups requires specification of the effect size in each group.")
        }
      }
      if(is.list(effect) && power.type == "a-priori"){   # special messages for a priori, given weights need to be provided 
        if(is.null(N) || length(N) == 1){
          warning("No or only a single sample weight provided in multiple group power analyses, assuming equal weights.")        
        }else if(!is.null(N) && length(N) != length(effect)){
          stop("A priori power analyses with multiple groups requires specification of sample size weights for each group via the N argument")      
        } 
      }
      if(is.list(effect) && (power.type == "post-hoc" || power.type == "compromise")){
        if(!is.null(N) && length(N) == 1){
          warning("Only single sample size provided in multiple group power analyses, assuming equal sample sizes for each group.")
        }else if(!is.null(N) && length(N) != length(effect)){
          stop("Power analyses with multiple groups requires specification of sample sizes for each group")        
        }
      }
      
      if(power.type != 'powerplot.byEffect'){
        if(is.null(effect)) stop('Effect is not defined.')
        lapply(effect, checkPositive, message = effect.measure)
      }
      
      if(effect.measure == "GFI" || effect.measure == "AGFI"){
        if(is.null(p)){
          stop("effect.measure GFI and AGFI require specification of p")
        }
        checkPositive(p)
      }
    }
    
    # power analyses based on covariance matrices  
    if((!is.null(SigmaHat) && is.null(Sigma)) || (!is.null(Sigma) && is.null(SigmaHat))){
      stop("Both SigmaHat and Sigma must be defined when effect is determined from covariance matrices")
    }
    if((!is.null(unlist(muHat)) && is.null(unlist(mu))) || (!is.null(unlist(mu)) && is.null(unlist(muHat)))){
      stop("Both muHat and mu must be defined when effect is determined from covariance matrices and meanstructures.")
    }
    if((!is.null(unlist(muHat)) || !is.null(unlist(mu))) && (is.null(Sigma) || is.null(SigmaHat))){
      stop("When effect is to be determined from covariance matrices and meanstructures, Sigma, SigmaHat, mu, and muHat must be provided.")
    }
    
    if(!is.null(SigmaHat) && !is.null(Sigma)){
      
      if(!is.null(modelH0)) warning("ModelH0 is ignored when SigmaHat is defined.")
      if(!is.null(effect) || !is.null(effect.measure))
        warning("Ignoring effect and effect.measure when Sigma and SigmaHat are defined.")
      
      # convert to list in any case so validiation below always applies 
      if(!is.list(Sigma)) Sigma <- list(Sigma)
      if(!is.list(SigmaHat)) SigmaHat <- list(SigmaHat)
      
      if(any(sapply(Sigma, ncol) != sapply(SigmaHat, ncol)) || any(sapply(Sigma, nrow) != sapply(SigmaHat, nrow)))
        stop("Sigma and SigmaHat must be of equal size")
      if(any(!sapply(c(Sigma, SigmaHat), isSymmetric)))
        stop("Sigma and SigmaHat must be symmetric square matrices")
      if(any(sapply(c(Sigma, SigmaHat), ncol) < 2))
        stop("Sigma and SigmaHat must be at least of dimension 2*2")
      if(any(sapply(c(Sigma, SigmaHat), function(x){sum(eigen(x)$values < 0) > 0})))
        stop("Sigma and SigmaHat must be positive definite")
      
      # now remove list structure for length = 1
      if(is.list(SigmaHat) && length(SigmaHat) == 1) SigmaHat <- SigmaHat[[1]]
      if(is.list(Sigma) && length(Sigma) == 1) Sigma <- Sigma[[1]]
      
      # multigroup case
      if(is.list(SigmaHat) || is.list(Sigma)){
        if(length(SigmaHat) != length(Sigma)) stop("Multiple group power analyses require specification of SigmaHat and Sigma for each group.")
        if(is.null(N) && power.type != "a-priori") stop("Multiple group power analyses require specification of N for each group (representing weights in a priori power analysis).")
        if(is.null(N) && power.type == "a-priori") warning("No sample weights provided, assuming equal sample sizes in each group.")
        if(length(N) == 1){
          warning("Only single sample size provided in multiple group power analyses, assuming equal sample sizes (weights in a priori power analyses) for each group.")
        }else if(length(SigmaHat) != length(N)){
          stop("Multiple group power analyses require specification of N for each group.")
        } 
        
        if(!is.null(unlist(muHat)) && !is.null(unlist(mu))){
          if(!is.list(muHat)) muHat <- list(muHat)
          if(!is.list(mu)) mu <- list(mu)
          if(length(muHat) != length(mu)) stop("Multiple group power analyses require specification of mu and muHat for each group.")
          if(length(mu) != length(Sigma)) stop("Multiple group power analyses require specification of mu and muHat for each group.")
          if(any(sapply(c(mu, muHat), is.na)))
            stop("mu and muHat must not contain NA values.")
          if(any(sapply(mu, length) != sapply(muHat, length)))
            stop("mu and muHat must be of same size.")
          if(is.list(Sigma) && any(sapply(mu, length) != sapply(Sigma, ncol)))
            stop("mu and muHat must be of same length as the columns/rows of Sigma and SigmaHat.")
        }
      }
      
    }
    
    ### TODO remove code duplication by merging the following with the preceding block
    # simulated power specifics  
  }else{
    if(is.null(Sigma) || is.null(modelH0)) stop("Simulated power requires specification of Sigma and modelH0.")
    if(!is.null(effect) || !is.null(effect.measure))
      warning("Ignoring effect and effect.measure in simulated power.")
    
    if(!is.list(Sigma)) Sigma <- list(Sigma)
    if(any(!sapply(Sigma, isSymmetric)))
      stop("Sigma must be symmetric square matrices")
    if(any(sapply(Sigma, ncol) < 2))
      stop("Sigma must be at least of dimension 2*2")
    if(any(sapply(Sigma, function(x){sum(eigen(x)$values < 0) > 0})))
      stop("Sigma must be positive definite")
    
    if(length(Sigma) == 1) Sigma <- Sigma[[1]]
    if(!is.null(mu) && length(mu) == 1) mu <- mu[[1]]
    if(is.list(Sigma)){
      if(!is.null(mu) && length(mu) != length(Sigma))
        stop("Multiple group power analyses require specification of mu for each group.")
      if(is.null(N) && power.type != "a-priori") stop("Multiple group power analyses require specification of N for each group (representing weights in a priori power analysis).")
      if(is.null(N) && power.type == "a-priori") warning("No sample weights provided, assuming equal sample sizes in each group.")
      if(length(N) == 1){
        warning("Only single sample size provided in multiple group power analyses, assuming equal sample sizes (weights in a priori power analyses) for each group.")
      }else if(length(Sigma) != length(N)){
        stop("Multiple group power analyses require specification of N for each group.")
      } 
    }else{
      if(!is.null(mu) && length(mu) != ncol(Sigma)) stop("Mu must have the same length as ncol(Sigma).")
    }

  }
  
  # specifics depending on type of power analyses
  if(power.type == "post-hoc" || power.type == "compromise" || (power.type == "a-priori" && is.list(effect))){
    if(is.null(N)) stop('N is not defined.')
    lapply(N, checkPositive, message = 'N')
  }
  
  if(power.type == "a-priori" || power.type == "post-hoc"){
    checkBounded(alpha)
  }
  
  if(power.type == "a-priori"){
    if(is.null(beta) && is.null(power))
      stop("Need to define either beta or power in a-priori power analyis")
    if(!is.null(beta) && !is.null(power) && power != (1 - beta))
      stop("Either set beta or set power, but not both.")
    if(!is.null(beta))
      checkBounded(beta)
    if(!is.null(power))
      checkBounded(power)
  }
  
  if(power.type == "compromise"){
    checkPositive(abratio)
  }
  
  # specifics for power plots
  if(power.type == "powerplot.byN"){
    checkBounded(alpha)
    checkBounded(power.max)
    checkBounded(power.min)
    checkPositive(steps)
    checkPositive(linewidth)
  }
  
  if(power.type == "powerplot.byEffect"){
    checkPositive(N)
    checkBounded(alpha)
    checkPositive(effect.min)
    checkPositive(effect.max)
    checkPositive(steps)
    checkPositive(linewidth)
  }

}


#' checkPositive
#'
#' Checks whether `x` is defined and a positive number, stop otherwise.
#' @param x x
#' @param message identifier for `x`
checkPositive <- function(x, message = NULL){
  if(is.null(message)) message <- deparse(substitute(x))
  if(is.null(x) || is.na(x) || x <= 0){
    stop(paste(message, " must be larger than zero"))
  }
}

#' checkBounded
#'
#' Checks whether x is defined and lies within the specified bound, stop otherwise.
#' @param x x
#' @param message identifier for x
#' @param bound the boundaries, array of size two
#' @param inclusive whether x might lie on boundary
checkBounded <- function(x, message = NULL, bound = c(0, 1), inclusive = FALSE){
  if(is.null(message)) message <- deparse(substitute(x))
  inv <- is.null(x) || is.na(x) || !is.numeric(x)
  if(!inv && !inclusive && (x <= bound[1] || x >= bound[2])) inv <- TRUE
  if(!inv && inclusive && (x < bound[1] || x > bound[2])) inv <- TRUE
  if(inv) stop(paste(message, "must must lie within", bound[1], 'and', bound[2]))
}

#' checkPositiveDefinite
#'
#' Checks whether `x` is positive definite, stop otherwise.
#' 
#' @param x x
#' @param message identifier for `x`
#' @param stop whether to stop or to throw a warning
checkPositiveDefinite <- function(x, message = NULL, stop = TRUE){
  if(is.null(message)) message <- deparse(substitute(x))
  checkSymmetricSquare(x)
  if(sum(eigen(x)$values < 0) > 0){
    if(stop) stop(paste(message, " must be positive definite"))
    if(!stop) warning(paste(message, " must be positive definite"))
  }
}

#' checkSymmetricSquare
#'
#' Checks whether `x` is a symmetric square matrix, stop otherwise.
#' 
#' @param x x
#' @param message identifier for `x`
checkSymmetricSquare <- function(x, message = NULL){
  if(is.null(message)) message <- deparse(substitute(x))
  checkSquare(x)
  if(!isSymmetric(x))
    stop(paste(message, " must be a symmetric square matrix"))
}

#' checkSquare
#'
#' Checks whether `x` is a square matrix, stop otherwise.
#' 
#' @param x x
#' @param message identifier for `x`
checkSquare <- function(x, message = NULL){
  if(is.null(message)) message <- deparse(substitute(x))
  if(is.null(x))
    stop(paste(message, " may not be NULL"))
  if(!is.numeric(x))
    stop(paste(message, " must contain numeric elements only"))
  if(!is.matrix(x))
    stop(paste(message, " must be a matrix"))
  if(ncol(x) != nrow(x))
    stop(paste(message, " must be a square matrix"))
}

#' checkPowerTypes
#'
#' Checks whether type is one of `'a-priori'`, `'post-hoc'`, or `'compromise'` (or respective shortcuts), stop otherwise.
#' 
#' @param type type
#' @return Returns cleaned type
checkPowerTypes <- function(type){
  if(is.null(type) || length(type) != 1 || typeof(type) != 'character') stop('Type must be one of a-priori, post-hoc, or compromise.')
  type <- tolower(trimws(type))
  if(type == 'a priori' || type == 'apriori' || type == 'a_priori' || type == 'ap') type <- 'a-priori'
  if(type == 'post hoc' || type == 'posthoc' || type == 'post_hoc' || type == 'ph') type <- 'post-hoc'
  if(type == 'co' || type == 'comp') type <- 'compromise'
  if(!type %in% c('a-priori', 'post-hoc', 'compromise')) stop('Type must be one of a-priori, post-hoc, or compromise.')
  type
}

#' checkComparisonModel
#'
#' Checks whether comparison is one of `'restricted'` or `'saturated'` (or respective shortcuts), stop otherwise.
#' 
#' @param comparison comparison
#' @return Returns cleaned comparison
checkComparisonModel <- function(comparison){
  if(is.null(comparison) || length(comparison) != 1 || typeof(comparison) != 'character') stop('Comparison model must be one of "saturated" or "restricted".')
  comparison <- tolower(trimws(comparison))
  if(comparison == 'saturated' || comparison == 'sat') comparison <- 'saturated'
  if(comparison == 'restricted' || comparison == 'restr') comparison <- 'restricted'
  if(!comparison %in% c('saturated', 'restricted')) stop('Comparison model must be one of "saturated" or "restricted"')
  comparison
}

#' checkNullEffect
#'
#' Checks whether `nullEffect` is one of the valid effects, stop otherwise.
#' 
#' @param nullEffect nullEffect
#' @param valid vector of valid effects 
#' @param message message 
#' @return Returns cleaned nullEffect
checkNullEffect <- function(nullEffect, valid, message = NULL){
  if(is.null(message)) message <- deparse(substitute(nullEffect))
  if(is.null(nullEffect)) stop(paste(message, 'must be defined.'))
  if(length(nullEffect) > 1) stop(paste(message, 'must contain a single hypothesis'))
  nullEffect <- unlist(lapply(nullEffect, function(x) tolower(trimws(x))))
  nullEffect <- gsub(" ", "", nullEffect, fixed = TRUE)
  if(any(unlist(lapply(nullEffect, function(x) !x %in% valid)))) stop(paste(message, 'must be one of', paste(valid, collapse = ' ')))
  nullEffect
}

#' checkDataGenerationTypes
#'
#' Checks whether data generation type is one of `'normal'`, `'IG'`, `'mnonr'`, `'RK'`, or `'VM'`, stop otherwise.
#' 
#' @param type type
#' @return Returns cleaned data generation type
checkDataGenerationTypes <- function(type){
  if(is.null(type) || length(type) != 1 || typeof(type) != 'character') stop('Data generation type is invalid.')
  type <- tolower(trimws(type))
  if(type == 'norm') type <- 'normal'
  if(!type %in% c('normal', 'ig', 'mnonr', 'rk', 'vm')) stop('Data generation type must be one of "normal", "IG", "mnonr", "RK", or "VM"')
  type
}

#' checkMissingTypes
#'
#' Checks whether missing generation type is one of `'mcar'`, `'mar'`, or `'nmar'`, stop otherwise.
#' 
#' @param type type
#' @return Returns cleaned data generation type
checkMissingTypes <- function(type){
  if(is.null(type) || length(type) != 1 || typeof(type) != 'character') stop('Missing mechanism must be one of "mcar", "mar", or "mnar".')
  type <- tolower(trimws(type))
  if(!type %in% c('mcar', 'mar', 'nmar')) stop('Missing mechanism must be one of "mcar", "mar", or "mnar"')
  type
}

#' checkEllipsis
#'
#' Checks whether `...` contains arguments related to loadings and to power, stop otherwise.
#' 
#' @param ... the parameters to search.
checkEllipsis <- function(...){
  args <- names(list(...))
  if(!any(c('Lambda', 'loadings', 'nIndicator', 'loadM') %in% args)) stop('Missing arguments specifying the factor model. Remember to set either Lambda, loadings, or nIndicator and loadM.')
  if(!('alpha' %in% args || 'abratio' %in% args)) stop('Missing arguments related to power analysis. Remember to set alpha, power, N, and/or abratio.')
}


#' getFittingFunctionFromEstimator
#'
#' get proper fitting function (to obtain sigmaHat) for chosen estimator
#' 
#' @param lavOptions lavOptions
#' @return fitting function
getFittingFunctionFromEstimator <- function(lavOptions){
  lavEstimML <- c('ML', 'MLM', 'MLMV', 'MLMVS', 'MLF', 'MLR')
  lavEstimWLS <- c('WLS')
  lavEstimDWLS <- c('DWLS', 'WLSM', 'WLSMV')
  lavEstimULS <- c('ULS', 'ULSM', 'ULSMV')
  
  ff <- 'ML'
  if(!is.null(lavOptions[['estimator']])){
    lavEst <- toupper(lavOptions[['estimator']])
    if(lavEst %in% lavEstimML) ff <- 'ML'
    if(lavEst %in% lavEstimWLS) ff <- 'WLS'
    if(lavEst %in% lavEstimDWLS) ff <- 'DWLS'
    if(lavEst %in% lavEstimULS) ff <- 'ULS'
  } 
  
  ff
}

#' getDiscrepancyFunctionFromFittingFunction
#'
#' get proper discrepancy function (to measure F0) from fitting function (to obtain SigmaHat)
#' 
#' @param fittingFunction fittingFunction
#' @return discrepancy function
getDiscrepancyFunctionFromFittingFunction <- function(fittingFunction){
  df <- 'ML'
  # two-stage estimators: estimate using reduced weight matrix, but obtain test-statistics using full weight matrix
  # this is currently (effectively) unused, since using the same approach in estimating and discrepancy assessement 
  # yields results closer to what lavaan does.
  if(fittingFunction %in% c('WLS', 'DWLS', 'ULS')) df <- 'WLS'
  df
}
