## NB:  doc in ../man/*.Rd  ***not*** auto generated
## FIXME: need to document S3 methods better (can we pull from r-forge version?)

##' Fit a linear mixed model (LMM)
lmer <- function(formula, data=NULL, REML = TRUE,
                 control = lmerControl(), start = NULL
               , verbose = 0L
               , subset, weights, na.action, offset
               , contrasts = NULL
               , devFunOnly=FALSE
                )
    ## , ...)
{
    mc <- mcout <- match.call()
    missCtrl <- missing(control)
    ## see functions in modular.R for the body ..
    if (!missCtrl && !inherits(control, "lmerControl")) {
        if(!is.list(control)) stop("'control' is not a list; use lmerControl()")
        ## back-compatibility kluge
        warning("passing control as list is deprecated: please use lmerControl() instead",
                immediate.=TRUE)
        control <- do.call(lmerControl, control)
    }
    ## if (!is.null(list(...)[["family"]])) {
    ##    warning("calling lmer with 'family' is deprecated; please use glmer() instead")
    ##    mc[[1]] <- quote(lme4::glmer)
    ##    if(missCtrl) mc$control <- glmerControl()
    ##    return(eval(mc, parent.frame(1L)))
    ## }
    mc$control <- control ## update for  back-compatibility kluge

    ## https://github.com/lme4/lme4/issues/50
    ## parse data and formula
    mc[[1]] <- quote(lme4::lFormula)
    lmod <- eval(mc, parent.frame(1L))
    mcout$formula <- lmod$formula
    lmod$formula <- NULL

    if (is.matrix(y <- model.response(lmod$fr)) && ncol(y) > 1) {
        stop("can't handle matrix-valued responses: consider using refit()")
    }

    ## create deviance function for covariance parameters
    lmod.. <- list(start=start, verbose=verbose, control=control)
    devfun <- do.call(mkLmerDevfun, c(lmod, lmod..))
    if (devFunOnly) return(devfun)
    rho <- environment(devfun)

    nobs <- nrow(lmod$fr)
    npar <- length(rho$lower)
    calc.derivs <- control$calc.derivs %||%
        (nobs < control$checkConv$check.conv.nobsmax &&
         npar < control$checkConv$check.conv.nparmax)
    
    if (identical(control$optimizer, "none"))
        stop("deprecated use of optimizer==\"none\"; use NULL instead")

    ## optimize deviance function over covariance parameters
    opt <- if (length(control$optimizer)==0) {
               s <- getStart(start, rho, 0L)
               list(par=s,fval=devfun(s),
                    conv=1000,message="no optimization")
           }  else {
               optimizeLmer(devfun, optimizer = control$optimizer,
                     restart_edge = control$restart_edge,
                     boundary.tol = control$boundary.tol,
                     control = control$optCtrl,
                     verbose=verbose,
                     start=start,
                     calc.derivs=calc.derivs,
                     force.calc.derivs=control$calc.derivs %||% FALSE,
                     use.last.params=control$use.last.params)
           }
    cc <- checkConv(attr(opt,"derivs"), opt$par,
                    ctrl = control$checkConv,
                    lbound = rho$lower,
                    ubound = rho$upper,
                    nobs = nobs,
                    ndim = npar)
    mkMerMod(rho, opt, lmod$reTrms, fr = lmod$fr,
             mc = mcout, lme4conv=cc) ## prepare output
}## { lmer }


##' Fit a generalized linear mixed model (GLMM)
glmer <- function(formula, data=NULL
                , family = gaussian
                , control = glmerControl()
                , start = NULL
                , verbose = 0L
                , nAGQ = 1L
                , subset, weights, na.action, offset, contrasts = NULL
                , mustart, etastart
                , devFunOnly = FALSE)
{
    if (!inherits(control, "glmerControl")) {
        if(!is.list(control)) stop("'control' is not a list; use glmerControl()")
        ## back-compatibility kluge
        if (class(control)[1]=="lmerControl") {
            warning("please use glmerControl() instead of lmerControl()",
                    immediate.=TRUE)
            control <-
                ## unpack sub-lists
                c(control[!names(control) %in% c("checkConv","checkControl")],
                  control$checkControl,control$checkConv)
            control["restart_edge"] <- NULL ## not implemented for glmer
        } else {
            msg <- "Use control=glmerControl(..) instead of passing a list"
            if(length(cl <- class(control))) {
                msg <- paste(msg, "of class", dQuote(cl[1]))
            }
            warning(msg, immediate.=TRUE)
        }

        control <- do.call(glmerControl, control)
    }
    mc <- mcout <- match.call()

    ## family-checking code duplicated here and in glFormula (for now) since
    ## we really need to redirect at this point; eventually deprecate formally
    ## and clean up
    if (is.character(family))
        family <- get(family, mode = "function", envir = parent.frame(2))
    if( is.function(family)) family <- family()
    if (isTRUE(all.equal(family, gaussian()))) {
        ## redirect to lmer (with warning)
        warning("calling glmer() with family=gaussian (identity link) as a shortcut to lmer() is deprecated;",
                " please call lmer() directly")
        mc[[1]] <- quote(lme4::lmer)
        mc["family"] <- NULL            # to avoid an infinite loop
        return(eval(mc, parent.frame()))
    }

    ## see https://github.com/lme4/lme4/issues/50
    ## parse the formula and data
    mc[[1]] <- quote(lme4::glFormula)
    glmod <- eval(mc, parent.frame(1L))
    mcout$formula <- glmod$formula
    glmod$formula <- NULL
    
    if (is.matrix(y <- model.response(glmod$fr))
        && ((family$family != "binomial" && ncol(y) > 1) ||
            (ncol(y) >2))) {
        stop("can't handle matrix-valued responses: consider using refit()")
    }

    ## create deviance function for covariance parameters
    nAGQinit <- if (control$nAGQ0initStep) 0L else nAGQ
    glmod.. <- list(nAGQ=nAGQinit, verbose=verbose, control=control)
    devfun <- do.call(mkGlmerDevfun, c(glmod, glmod..))
    if (devFunOnly && nAGQ == 0L) return(devfun)
    rho <- environment(devfun)

    if (nAGQinit == 0L) {
        start0 <-
        if (nAGQ > 0L && is.list(start) && !is.null(names(start)))
            start[!names(start) %in% c("fixef", "beta")]
        else start
        ## optimize deviance function over covariance parameters
        ## FIXME: allow calc.derivs, use.last.params, etc. if nAGQ=0
        opt <- optimizeGlmer(devfun,
                             optimizer = control$optimizer[[1]],
                             ## DON'T try fancy edge tricks unless nAGQ=0 explicitly set
                             restart_edge=if (nAGQ==0) control$restart_edge else FALSE,
                             boundary.tol=if (nAGQ==0) control$boundary.tol else 0,
                             control = control$optCtrl,
                             start = start0,
                             nAGQ = 0,
                             verbose=verbose,
                             calc.derivs=FALSE)

        if (nAGQ > 0L) {
        ## update deviance function to include fixed effects
        devfun <- updateGlmerDevfun(devfun, glmod$reTrms, nAGQ = nAGQ)
        if (devFunOnly) return(devfun)
        start <- updateStart(start, opt$par)
        }
    }

    nobs <- nrow(glmod$fr)
    npar <- length(rho$lower)
    calc.derivs <- control$calc.derivs %||%
        (nobs < control$checkConv$check.conv.nobsmax &&
         npar < control$checkConv$check.conv.nparmax)

    if (nAGQ > 0L)
        ## optimize deviance function over covariance parameters and
        ## fixed effects
        opt <- optimizeGlmer(devfun,
                             optimizer = control$optimizer[[2]],
                             restart_edge=control$restart_edge,
                             boundary.tol=control$boundary.tol,
                             control = control$optCtrl,
                             start=start,
                             nAGQ=nAGQ,
                             verbose = verbose,
                             calc.derivs=calc.derivs,
                             use.last.params=control$use.last.params)

    cc <- if (!calc.derivs) NULL else {
        if (verbose > 10) cat("checking convergence\n")
        checkConv(attr(opt,"derivs"),opt$par,
                  ctrl = control$checkConv,
                  lbound = rho$lower,
                  ubound = rho$upper,
                  nobs = nobs,
                  ndim = npar)
    }

    ## prepare output
    mkMerMod(rho, opt, glmod$reTrms, fr = glmod$fr,
             mc = mcout, lme4conv=cc)

}## {glmer}

##' Fit a nonlinear mixed-effects model
nlmer <- function(formula, data=NULL, control = nlmerControl(), start = NULL, verbose = 0L,
                  nAGQ = 1L, subset, weights, na.action, offset,
                  contrasts = NULL, devFunOnly = FALSE)
{

    vals <- nlformula(mc <- match.call())
    p <- ncol(X <- vals$X)
    if ((rankX <- rankMatrix(X)) < p)
        stop(gettextf("rank of X = %d < ncol(X) = %d", rankX, p))

    rho <- list2env(list(verbose=verbose,
                         tolPwrss=0.001, # this is reset to the tolPwrss argument's value later
                         resp=vals$respMod),
                    parent=parent.frame())
    rho$pp <- do.call(merPredD$new,
                      c(vals$reTrms[c("Zt","theta","Lambdat","Lind")],
                        list(X=X, n=length(vals$respMod$mu), Xwts=vals$respMod$sqrtXwt,
                             beta0=qr.coef(qr(X), unlist(lapply(vals$pnames, get,
                             envir = vals$respMod$nlenv))))))
    rho$mkPar <- mkMkPar(vals$reTrms$reCovs)
    rho$mkTheta <- mkMkTheta(vals$reTrms$reCovs)
    rho$u0 <- rho$pp$u0
    rho$beta0 <- rho$pp$beta0
    ## deviance as a function of theta only :
    devfun <- mkdevfun(rho, 0L, verbose=verbose, control=control)
    if (devFunOnly && !nAGQ) return(devfun)
    devfun(rho$mkPar(rho$pp$theta)) # initial coarse evaluation to get u0 and beta0
    rho$u0 <- rho$pp$u0
    rho$beta0 <- rho$pp$beta0
    rho$tolPwrss <- control$tolPwrss # Reset control parameter (the initial optimization is coarse)
    rho$nAGQ <- nAGQ
    
    ## set lower and upper bounds: if user-specified, select
    ##  only the ones corresponding to random effects
    lower <- vals$reTrms$lower
    if (!is.null(lwr <- control$optCtrl$lower)) {
        lower <- lwr[seq_along(lower)]
        control$optCtrl$lower <- NULL
    }
    upper <- vals$reTrms$upper
    if (!is.null(upr <- control$optCtrl$upper)) {
        upper <- upr[seq_along(upper)]
        control$optCtrl$upper <- NULL
    }

    opt <- optwrap(control$optimizer[[1]], devfun,
                   rho$mkPar(rho$pp$theta),
                   lower=lower,
                   upper=upper,
                   control=control$optCtrl,
                   adj=FALSE)

    if (nAGQ > 0L) {
        ## set lower/upper to values already harvested from control$optCtrl$upper
        lower <- lwr %||% c(lower, rep.int(-Inf, length(rho$pp$beta0)))
        upper <- upr %||% c(upper, rep.int( Inf, length(rho$pp$beta0)))
        rho$dpars <- seq_len(length(lower) - length(rho$pp$beta0))
        rho$u0    <- rho$pp$u0
        rho$beta0 <- pmin(upper[-rho$dpars],
                          pmax(rho$pp$beta0, lower[-rho$dpars]))
        if (nAGQ > 1L) {
            if (length(vals$reTrms$flist) != 1L || length(vals$reTrms$cnms[[1]]) != 1L)
                stop("nAGQ > 1 is only available for models with a single, scalar random-effects term")
            rho$fac <- vals$reTrms$flist[[1]]
        }
        devfun <- mkdevfun(rho, nAGQ, verbose=verbose, control=control)
        if (devFunOnly) return(devfun)

        opt <- optwrap(control$optimizer[[2]], devfun,
                       par = c(rho$mkPar(rho$pp$theta), rho$beta0),
                       lower = lower,
                       upper = upper,
                       control = control$optCtrl,
                       adj = TRUE, verbose=verbose)

    }
    mkMerMod(environment(devfun), opt, vals$reTrms, fr = vals$frame, mc = mc)
}## {nlmer}

## R 3.1.0 devel [2013-08-05]: This does not help yet
if(getRversion() >= "3.1.0") utils::suppressForeignCheck("nlmerAGQ")
if(getRversion() < "3.1.0") dontCheck <- identity

## *not* exported (had help page till early 2018)
## -> issue #92: -> also look at devfun2() in  ./profile.R (which returns class!)
##' Create a deviance evaluation function from a predictor and a response module
##' @param rho an `environment` already containing `verbose` and tolPwrss
##' @param nAGQ for glmer/nlmer: #{AGQ steps}; 0 <==> Laplace
##' @param maxit maximal number of PIRLS iterations
##' @param verbose integer specifying if outputs should be produced
##' @param control a list as from lmerControl() etc
mkdevfun <- function(rho, nAGQ=1L, maxit = if(extends(rho.cld, "nlsResp")) 300L else 100L,
                     verbose=0, control=list()) {
    ## FIXME: should nAGQ be automatically embedded in rho?
    stopifnot(is.environment(rho), ## class definition, compute and save :
              extends(rho.cld <- getClass(class(rho$resp)), "lmResp"))

    ## silence R CMD check warnings *locally* in this function
    ## (clearly preferred to using globalVariables() !]
    fac <- pp <- resp <- lp0 <- compDev <- dpars <- baseOffset <- tolPwrss <-
        pwrssUpdate <- ## <-- even though it's a function below
        GQmat <- nlmerAGQ <- mkTheta <- NULL

    ## The deviance function (to be returned, with 'rho' as its environment):
    ff <-
    if (extends(rho.cld, "lmerResp")) {
        rho$lmer_Deviance <- lmer_Deviance
        function(par)
            .Call(lmer_Deviance, pp$ptr(), resp$ptr(),
                  mkTheta(as.double(par)))
    } else if (extends(rho.cld, "glmResp")) {
        ## control values will override rho values *if present*
        if (!is.null(tp <- control$tolPwrss)) rho$tolPwrss <- tp
        if (!is.null(cd <- control$ compDev)) rho$compDev <- cd
        if (nAGQ == 0L)
            function(par) {
                resp$updateMu(lp0)
                pp$setTheta(mkTheta(as.double(par)))
                p <- pwrssUpdate(pp, resp, tol=tolPwrss, GQmat=GHrule(0L),
                                 compDev=compDev, maxit=maxit, verbose=verbose)
                resp$updateWts()
                p
            }
        else  ## nAGQ > 0
            function(pars) {
                ## pp$setDelu(rep(0, length(pp$delu)))
                resp$setOffset(baseOffset)
                resp$updateMu(lp0)
                pp$setTheta(mkTheta(as.double(pars[dpars])))
                spars <- as.double(pars[-dpars])
                offset <- if (length(spars)==0) baseOffset else baseOffset + pp$X %*% spars
                resp$setOffset(offset)
                p <- pwrssUpdate(pp, resp, tol=tolPwrss, GQmat=GQmat,
                                 compDev=compDev, grpFac=fac, maxit=maxit, verbose=verbose)
                resp$updateWts()
                p
            }
    } else if (extends(rho.cld, "nlsResp")) {
        if (nAGQ <= 1L) {
            rho$nlmerLaplace <- nlmerLaplace
            rho$tolPwrss <- control$tolPwrss
            rho$maxit <- maxit
            switch(nAGQ + 1L,
                   function(par)
                       .Call(nlmerLaplace, pp$ptr(), resp$ptr(),
                             as.double(par),
                             as.double(u0), as.double(beta0),
                             verbose, FALSE, tolPwrss, maxit),
                   function(pars)
                       .Call(nlmerLaplace, pp$ptr(), resp$ptr(),
                             as.double(pars[dpars]),
                             as.double(u0), as.double(pars[-dpars]),
                             verbose, TRUE, tolPwrss, maxit))
        } else {
            stop("nAGQ > 1  not yet implemented for nlmer models")
            rho$nlmerAGQ <- nlmerAGQ
            rho$GQmat    <- GHrule(nAGQ)
            ## function(pars) {
            ## .Call(nlmerAGQ, ## <- dontCheck(nlmerAGQ)  should work according to docs but does not
            ## pp$ptr(), resp$ptr(), fac, GQmat, pars[dpars],
            ## u0, pars[-dpars], tolPwrss)
            ##}
        }
    }
    else stop("code not yet written")
    environment(ff) <- rho
    ff
}

## Determine a step factor that will reduce the pwrss
##
## The penalized, weighted residual sum of squares (pwrss) is the sum
## of the weighted residual sum of squares from the resp module and
## the squared length of u from the predictor module.  The predictor module
## contains a base value and an increment for the coefficients.
## @title Determine a step factor
## @param pp predictor module
## @param resp response module
## @param verbose logical value determining verbose output
## @return NULL if successful
## @note Typically all this is done in the C++ code.
##     The R code is for debugging and comparisons of
##     results.
## stepFac <- function(pp, resp, verbose, maxSteps = 10) {
##     stopifnot(is.numeric(maxSteps), maxSteps >= 2)
##     pwrss0 <- resp$wrss() + pp$sqrL(0)
##     for (fac in 2^(-(0:maxSteps))) {
##      wrss <- resp$updateMu(pp$linPred(fac))
##      pwrss1 <- wrss + pp$sqrL(fac)
##      if (verbose > 3L)
##          cat(sprintf("pwrss0=%10g, diff=%10g, fac=%6.4f\n",
##                      pwrss0, pwrss0 - pwrss1, fac))
##      if (pwrss1 <= pwrss0) {
##          pp$installPars(fac)
##          return(NULL)
##      }
##     }
##     stop("step factor reduced below ",signif(2^(-maxSteps),2)," without reducing pwrss")
## }

RglmerWrkIter <- function(pp, resp, uOnly=FALSE) {
    pp$updateXwts(resp$sqrtWrkWt())
    pp$updateDecomp()
    pp$updateRes(resp$wtWrkResp())
    if (uOnly) pp$solveU() else pp$solve()
    resp$updateMu(pp$linPred(1))        # full increment
    resp$resDev() + pp$sqrL(1)
}

##' @param pp pred module
##' @param resp resp module
##' @param tol numeric tolerance
##' @param GQmat matrix of Gauss-Hermite quad info
##' @param compDev compute in C++ (as opposed to doing as much as possible in R)
##' @param grpFac grouping factor (normally found in environment ..)
##' @param verbose verbosity, of course
glmerPwrssUpdate <- function(pp, resp, tol, GQmat, compDev=TRUE, grpFac=NULL, maxit = 70L, verbose=0) {
    nAGQ <- nrow(GQmat)
    if (compDev) {
        if (nAGQ < 2L)
            return(.Call(glmerLaplace, pp$ptr(), resp$ptr(),
                         nAGQ, tol, as.integer(maxit),
                         verbose))
        return(.Call(glmerAGQ, pp$ptr(), resp$ptr(),
                     tol, as.integer(maxit),
                     GQmat, grpFac, verbose))
    }
 ### does this show anywhere ??? [i.e. is it ever used in our checks/examples/scripts/vignettes ?
 ### message("glmerPwrssUpdate(*, compDev=FALSE)  --> using more R, no direct .Call() to C.") # [DBG] only
    oldpdev <- .Machine$double.xmax
    uOnly   <- nAGQ != 0L
    i <- 0
    repeat {
        ## oldu <- pp$delu
        ## olddelb <- pp$delb
        pdev <- RglmerWrkIter(pp, resp, uOnly=uOnly)
        if (verbose > 2) cat(i,": ",pdev,"\n",sep="")
        ## check convergence first so small increases don't trigger errors
        if (is.na(pdev)) stop("encountered NA in PWRSS update")
        if (abs((oldpdev - pdev) / pdev) < tol)
            break
        ## if (pdev > oldpdev) {
        ##     ## try step-halving
        ##     k <- 0
        ##     while (k < 10 && pdev > oldpdev) {
        ##         pp$setDelu((oldu + pp$delu)/2.)
        ##         if (!uOnly) pp$setDelb((olddelb + pp$delb)/2.)
        ##         pdev <- RglmerWrkIter(pp, resp, uOnly=uOnly)
        ##         k <- k+1
        ##     }
        ## }
        if (pdev > oldpdev) stop("PIRLS update failed")
        oldpdev <- pdev
        i <- i+1
    }
    resp$Laplace(pp$ldL2(), 0., pp$sqrL(1))  ## FIXME: should 0. be pp$ldRX2 ?
}

## create a deviance evaluation function that uses the sigma parameters
## df2 <- function(dd) {
##     stopifnot(is.function(dd),
##            length(formals(dd)) == 1L,
##            is((rem <- (rho <- environment(dd))$rem), "Rcpp_reModule"),
##            is((fem <- rho$fem), "Rcpp_deFeMod"),
##            is((resp <- rho$resp), "Rcpp_lmerResp"),
##            all((lower <- rem$lower) == 0))
##     Lind <- rem$Lind
##     n <- length(resp$y)
##     function(pars) {
##      sigma <- pars[1]
##      sigsq <- sigma * sigma
##      sigmas <- pars[-1]
##      theta <- sigmas/sigma
##      rem$theta <- theta
##      resp$updateMu(numeric(n))
##      solveBetaU(rem, fem, resp$sqrtXwt, resp$wtres)
##      resp$updateMu(rem$linPred1(1) + fem$linPred1(1))
##      n * log(2*pi*sigsq) + (resp$wrss + rem$sqrLenU)/sigsq + rem$ldL2
##     }
## }

## bootMer() ---> now in ./bootMer.R


## Methods for the merMod class

## Anova for merMod objects
##
## @title anova() for merMod objects
## @param a merMod object
## @param ...   further such objects
## @param refit should objects be refitted with ML (if applicable)
## @return an "anova" data frame; the traditional (S3) result of anova()
anovaLmer <- function(object, ..., refit = TRUE, model.names=NULL) {
    mCall <- match.call(expand.dots = TRUE)
    dots <- list(...)
    .sapply <- function(L, FUN, ...) unlist(lapply(L, FUN, ...))
    modp <- (as.logical(vapply(dots, is, NA, "merMod")) |
             as.logical(vapply(dots, is, NA, "lm")))
    if (any(modp)) {   ## multiple models - form table
        ## opts <- dots[!modp]
        mods <- c(list(object), dots[modp])
        nobs.vec <- vapply(mods, nobs, 1L)
        if (var(nobs.vec) > 0)
            stop("models were not all fitted to the same size of dataset")

        ## model names
        if (is.null(mNms <- model.names))
            mNms <- vapply(as.list(mCall)[c(FALSE, TRUE, modp)], deparse1, "")

        ## HACK to try to identify model names in situations such as
        ## 'do.call(anova,list(model1,model2))' where the model names
        ## are lost in the call stack ... this doesn't quite work but might
        ## be useful for future attempts?
        ## maxdepth <- -2
        ## depth <- -1
        ## while (depth >= maxdepth &
        ##        all(grepl("S4 object of class structure",mNms))) {
        ##     xCall <- match.call(call=sys.call(depth))
        ##     mNms <- .sapply(as.list(xCall)[c(FALSE, TRUE, modp)], deparse)
        ##     depth <- depth-1
        ## }
        ## if (depth < maxdepth) {
        if (any(substr(mNms, 1,4) == "new(") ||
            any(duplicated(mNms)) || ## <- only if S4 objects are *not* properly deparsed
            max(nchar(mNms)) > 200) {
            warning("failed to find model names, assigning generic names")
            mNms <- paste0("MODEL",seq_along(mNms))
        }
        if (length(mNms) != length(mods))
            stop("model names vector and model list have different lengths")
        names(mods) <- sub("@env$", '', mNms) # <- hack
        models.reml <- vapply(mods, function(x) is(x,"merMod") && isREML(x), NA)
        models.GHQ <- vapply(mods, function(x) is(x,"glmerMod") && getME(x,"devcomp")$dims[["nAGQ"]]>1 , NA)
        if (any(models.GHQ) && any(vapply(mods, function(x) is(x,"glm"), NA)))
            stop("GLMMs with nAGQ>1 have log-likelihoods incommensurate with glm() objects")
        if (refit) {
            ## message only if at least one models is REML:
            if (any(models.reml)) message("refitting model(s) with ML (instead of REML)")
            mods[models.reml] <- lapply(mods[models.reml], refitML)
        } else { ## check that models are consistent (all REML or all ML)
            if(any(models.reml) && any(!models.reml))
                warning("some models fit with REML = TRUE, some not")
        }
        ## devs <- sapply(mods, deviance)
        llks <- lapply(mods, logLik)
        ## Order models by increasing degrees of freedom:
        ii <- order(npar <- vapply(llks, attr, FUN.VALUE=numeric(1), "df"))
        mods <- mods[ii]
        llks <- llks[ii]
        npar   <- npar  [ii]
        calls <- lapply(mods, getCall)
        data <- lapply(calls, `[[`, "data")
        if(!all(vapply(data, identical, NA, data[[1]])))
            stop("all models must be fit to the same data object")
        header <- paste("Data:", abbrDeparse(data[[1]]))
        subset <- lapply(calls, `[[`, "subset")
        if(!all(vapply(subset, identical, NA, subset[[1]])))
            stop("all models must use the same subset")
        if (!is.null(subset[[1]]))
            header <- c(header, paste("Subset:", abbrDeparse(subset[[1]])))
        llk <- unlist(llks)
        chisq <- 2 * pmax(0, c(NA_real_, diff(llk)))
        dfChisq <- c(NA_integer_, diff(npar))
        val <- data.frame(npar = npar,
                          ## afraid to swap in vapply here; wondering
                          ##   why .sapply was needed in the first place ...
                          AIC = .sapply(llks, AIC), # FIXME? vapply()
                          BIC = .sapply(llks, BIC), #  "       "
                          logLik = llk,
                          "-2*log(L)" = -2*llk,
                          Chisq = chisq,
                          Df = dfChisq,
                          "Pr(>Chisq)" = ifelse(dfChisq==0,NA_integer_,pchisq(chisq, dfChisq, lower.tail = FALSE)),
                          row.names = names(mods), check.names = FALSE)
        class(val) <- c("anova", class(val))
        forms <- lapply(lapply(calls, `[[`, "formula"), deparse1)
        structure(val,
                  heading = c(header, "Models:",
                              paste(rep.int(names(mods), lengths(forms)),
                                    unlist(forms), sep = ": ")))
    }
    else { ## ------ single model ---------------------
        if (length(dots)>0) {
            warnmsg <- "additional arguments ignored"
            nd <- names(dots)
            nd <- nd[nzchar(nd)]
            if (length(nd)>0) {
                warnmsg <- paste0(warnmsg,": ",
                                  paste(sQuote(nd),collapse=", "))
            }
            warning(warnmsg)
        }
        dc <- getME(object, "devcomp")
        X <- getME(object, "X")
        stopifnot(length(asgn <- attr(X, "assign")) == dc$dims[["p"]])
        ss <- as.vector(object@pp$RX() %*% object@beta)^2
        names(ss) <- colnames(X)
        terms <- terms(object)
        nmeffects <- attr(terms, "term.labels")[unique(asgn)]
        if ("(Intercept)" %in% names(ss))
            nmeffects <- c("(Intercept)", nmeffects)
        ss <- unlist(lapply(split(ss, asgn), sum))
        stopifnot(length(ss) == length(nmeffects))
        df <- lengths(split(asgn, asgn))
        ## dfr <- unlist(lapply(split(dfr, asgn), function(x) x[1]))
        ms <- ss/df
        f <- ms/(sigma(object)^2)
        ## No longer provide p-values, but still the F statistic (may not be F distributed):
        ##
        ## P <- pf(f, df, dfr, lower.tail = FALSE)
        ## table <- data.frame(df, ss, ms, dfr, f, P)
        table <- data.frame(df, ss, ms, f)
        dimnames(table) <-
            list(nmeffects,
                 ## c("npar", "Sum Sq", "Mean Sq", "Denom", "F value", "Pr(>F)"))
                 c("npar", "Sum Sq", "Mean Sq", "F value"))
        if ("(Intercept)" %in% nmeffects)
            table <- table[-match("(Intercept)", nmeffects), ]
        structure(table, heading = "Analysis of Variance Table",
                  class = c("anova", "data.frame"))
    }
}## {anovaLmer}

##' @importFrom stats anova
##' @S3method anova merMod
anova.merMod <- anovaLmer

##' @S3method as.function merMod
as.function.merMod <- function(x, ...) {
    reCovs <- getReCovs(x)
    rho <- list2env(list(resp  = x@resp$copy(),
                         pp    = x@pp$copy(),
                         lower = getLower(x),
                         upper = getUpper(x),
                         mkPar = mkMkPar(reCovs),
                         mkTheta = mkMkTheta(reCovs)),
                    parent=as.environment("package:lme4"))
    ## FIXME: extract verbose [, maxit] and control
    mkdevfun(rho, getME(x, "devcomp")$dims[["nAGQ"]], ...)
}

## coef() method for all kinds of "mer", "*merMod", ... objects
## ------  should work with fixef() + ranef()  alone
coefMer <- function(object, ...)
{
    if(...length())
        warning('arguments named ', paste(sQuote(...names()), collapse = ", "),
                ' ignored')
    fef <- data.frame(rbind(fixef(object)), check.names = FALSE)
    ref <- ranef(object, condVar = FALSE)
    ## check for variables in RE but missing from FE, fill in zeros in FE accordingly
    refnames <- unlist(lapply(ref,colnames))
    nmiss <- length(missnames <- setdiff(refnames,names(fef)))
    if (nmiss > 0) {
        fillvars <- setNames(data.frame(rbind(rep(0,nmiss))),missnames)
        fef <- cbind(fillvars,fef)
    }
    val <- lapply(ref, function(x)
                  fef[rep.int(1L, nrow(x)),,drop = FALSE])
    for (i in seq_along(val)) {
        refi <- ref[[i]]
        row.names(val[[i]]) <- row.names(refi)
        nmsi <- colnames(refi)
        if (!all(nmsi %in% names(fef)))
            stop("unable to align random and fixed effects")
        for (nm in nmsi) val[[i]][[nm]] <- val[[i]][[nm]] + refi[,nm]
    }
    class(val) <- "coef.mer"
    val
} ##  {coefMer}

##' @importFrom stats coef
##' @S3method coef merMod
coef.merMod <- coefMer

## FIXME: should these values (i.e. ML criterion for REML models
##  and vice versa) be computed and stored in the object in the first place?
##' @importFrom stats deviance
##' @S3method deviance merMod
deviance.merMod <- function(object, REML = NULL, ...) {
                            ## type = c("conditional", "unconditional", "penalized"),
                            ## relative = TRUE, ...) {
    if (isGLMM(object)) {
        return(sum(residuals(object,type="deviance")^2))
        ## ------------------------------------------------------------
        ## proposed change to deviance function for GLMMs
        ## ------------------------------------------------------------
        ## @param type Type of deviance (can be unconditional,
        ## penalized, conditional)
        ## @param relative Should deviance be shifted relative to a
        ## saturated model? (only available with type == penalized or
        ## conditional)
        ## ------------------------------------------------------------
        ## ans <- switch(type[1],
        ##               unconditional = {
        ##                   if (relative) {
        ##                       stop("unconditional and relative deviance is undefined")
        ##                   }
        ##                   c(-2 * logLik(object))
        ##               },
        ##               penalized = {
        ##                   sqrL <- object@pp$sqrL(1)
        ##                   if (relative) {
        ##                       object@resp$resDev() + sqrL
        ##                   } else {
        ##                       useSc <- unname(getME(gm1, "devcomp")$dims[["useSc"]])
        ##                       qLog2Pi <- unname(getME(object, "q")) * log(2 * pi)
        ##                       object@resp$aic() - (2 * useSc) + sqrL + qLog2Pi
        ##                   }
        ##               },
        ##               conditional = {
        ##                   if (relative) {
        ##                       object@resp$resDev()
        ##                   } else {
        ##                       useSc <- unname(getME(gm1, "devcomp")$dims[["useSc"]])
        ##                       object@resp$aic() - (2 * useSc)
        ##                   }
        ##               })
        ## return(ans)
    }
    if (isREML(object) && is.null(REML)) {
        warning("deviance() is deprecated for REML fits; use REMLcrit for the REML criterion or deviance(.,REML=FALSE) for deviance calculated at the REML fit")
        return(devCrit(object, REML=TRUE))
    }
    devCrit(object, REML=FALSE)
}

REMLcrit <- function(object) {
    devCrit(object, REML=TRUE)
}

## original deviance.merMod -- now wrapped by REMLcrit
## REML=NULL:
##    if REML fit return REML criterion
##    if ML fit, return deviance
## REML=TRUE:
##    if not LMM, stop.
##    if ML fit, compute and return REML criterion
##    if REML fit, return REML criterion
## REML=FALSE:
##    if ML fit, return deviance
##    if REML fit, compute and return deviance
devCrit <- function(object, REML = NULL) {
    ## cf. (1) lmerResp::Laplace in respModule.cpp
    ##     (2) section 5.6 of lMMwR, listing lines 34-42
    if (isTRUE(REML) && !isLMM(object))
        stop("can't compute REML deviance for a non-LMM")
    cmp <- object@devcomp$cmp
    if (is.null(REML) || is.na(REML[1]))
        REML <- isREML(object)
    if (REML) {
        if (isREML(object)) {
            cmp[["REML"]]
        } else {
            ## adjust ML results to REML
            lnum <- log(2*pi*cmp[["pwrss"]])
            n <- object@devcomp$dims[["n"]]
            nmp <- n - length(object@beta)
            ldW <- sum(log(weights(object, method = "prior")))
            - ldW + cmp[["ldL2"]] + cmp[["ldRX2"]] + nmp*(1 + lnum - log(nmp))
        }
    } else {
        if (!isREML(object)) {
            cmp[["dev"]]
        } else {
            ## adjust REML results to ML
            n <- object@devcomp$dims[["n"]]
            lnum <- log(2*pi*cmp[["pwrss"]])
            ldW <- sum(log(weights(object, method = "prior")))
            - ldW + cmp[["ldL2"]] + n*(1 + lnum - log(n))
        }
    }
}

## copied from stats:::safe_pchisq
safe_pchisq <- function (q, df, ...) {
    df[df <= 0] <- NA
    pchisq(q = q, df = df, ...)
}

##' @importFrom stats drop1
##' @S3method drop1 merMod
drop1.merMod <- function(object, scope, scale = 0, test = c("none", "Chisq", "user"),
                         k = 2, trace = FALSE,
                         sumFun=NULL, ...) {
    evalhack <- "formulaenv"
    test <- match.arg(test)
    if ((test=="user" && is.null(sumFun)) ||
        ((test!="user" && !is.null(sumFun))))
        stop(sQuote("sumFun"),' must be specified if (and only if) test=="user"')
    tl <- attr(terms(object), "term.labels")
    if(missing(scope)) scope <- drop.scope(object)
    else {
        if(!is.character(scope)) {
            scope <- attr(terms(getFixedFormula(update.formula(object, scope))),
                                "term.labels")
        }
        if(!all(match(scope, tl, 0L) > 0L))
            stop("scope is not a subset of term labels")
    }
    ns <- length(scope)
    if (is.null(sumFun)) {
        sumFun <- function(x,scale,k,...)
            setNames(extractAIC(x,scale,k,...),c("df","AIC"))
    }
    ss <- sumFun(object, scale=scale, k=k, ...)
    ans <- matrix(nrow = ns + 1L, ncol = length(ss),
                  dimnames =  list(c("<none>", scope), names(ss)))
    ans[1, ] <- ss
    n0 <- nobs(object, use.fallback = TRUE)
    env <- environment(formula(object)) # perhaps here is where trouble begins??
    for(i in seq_along(scope)) {  ## was seq(ns), failed on empty scope
        tt <- scope[i]
        if(trace > 1) {
            cat("trying -", tt, "\n", sep='')
            flush.console()
        }
        ## FIXME: make this more robust, somehow?
        ## three choices explored so far:
        ##  (1) evaluate nfit in parent frame: tests in inst/tests/test-formulaEval.R
        ##      will fail on lapply(m_data_List,drop1)
        ##      (formula environment contains r,x,y,z but not d)
        ##  (2) evaluate nfit in frame of formula: tests will fail when data specified and formula is character
        ##  (3) update with data=NULL: fails when ...
        ##
        if (evalhack %in% c("parent","formulaenv")) {
            nfit <- update(object,
                           as.formula(paste("~ . -", tt)),
                           evaluate = FALSE)
            ## nfit <- eval(nfit, envir = env) # was  eval.parent(nfit)
            if (evalhack=="parent") {
                nfit <- eval.parent(nfit)
            } else if (evalhack=="formulaenv") {
                nfit <- eval(nfit,envir=env)
            }
        } else {
            nfit <- update(object,
                           as.formula(paste("~ . -", tt)),data=NULL,
                           evaluate = FALSE)
            nfit <- eval(nfit,envir=env)
        }
        if (test=="user") {
            ans[i+1, ] <- sumFun(object, nfit, scale=scale, k=k, ...)
        } else {
            ans[i+1, ] <- sumFun(nfit, scale, k = k, ...)
        }
        nnew <- nobs(nfit, use.fallback = TRUE)
        if(all(is.finite(c(n0, nnew))) && nnew != n0)
            stop("number of rows in use has changed: remove missing values?")
    }
    if (test=="user") {
        aod <- as.data.frame(ans)
    } else {
        dfs <- ans[1L, 1L] - ans[, 1L]
        dfs[1L] <- NA
        aod <- data.frame(npar = dfs, AIC = ans[,2])
        if(test == "Chisq") {
            ## reconstruct deviance from AIC (ugh)
            dev <- ans[, 2L] - k*ans[, 1L]
            dev <- dev - dev[1L] ; dev[1L] <- NA
            nas <- !is.na(dev)
            P <- dev
            P[nas] <- safe_pchisq(dev[nas], dfs[nas], lower.tail = FALSE)
            aod[, c("LRT", "Pr(Chi)")] <- list(dev, P)
        } else if (test == "F") {
            ## FIXME: allow this if denominator df are specified externally?
            stop("F test STUB -- unfinished maybe forever")
            dev <- ans[, 2L] - k*ans[, 1L]
            dev <- dev - dev[1L] ; dev[1L] <- NA
            nas <- !is.na(dev)
            P <- dev
            P[nas] <- safe_pchisq(dev[nas], dfs[nas], lower.tail = FALSE)
            aod[, c("LRT", "Pr(F)")] <- list(dev, P)
        }
    }
    head <- c("Single term deletions", "\nModel:", deparse(formula(object)),
              if(scale > 0) paste("\nscale: ", format(scale), "\n"))
    if (!is.null(method <- attr(ss,"method"))) {
        head <- c(head,"Method: ",method,"\n")
    }
    structure(aod, heading = head, class = c("anova", "data.frame"))
}

##' @importFrom stats extractAIC
##' @S3method extractAIC merMod
extractAIC.merMod <- function(fit, scale = 0, k = 2, ...) {
    L <- logLik(refitML(fit))
    edf <- attr(L,"df")
    c(edf,-2*L + k*edf)
}

##' @importFrom stats family
##' @S3method family merMod
family.merMod <- function(object, ...) family(object@resp, ...)

##' @S3method family glmResp
family.glmResp <- function(object, ...) {
                                        # regenerate initialize
                                        # expression if necessary

    ## FIXME: may fail with user-specified/custom family?
    ## should be obsolete
    if(is.null(object$family$initialize))
        return(do.call(object$family$family,
                       list(link=object$family$link)))

    object$family
}

##' @S3method family lmResp
family.lmResp <- function(object, ...) gaussian()

##' @S3method family nlsResp
family.nlsResp <- function(object, ...) gaussian()

##' @importFrom stats fitted
##' @S3method fitted merMod
fitted.merMod <- function(object, ...) {
    xx <- object@resp$mu
    if (length(xx)==0) {
        ## handle 'fake' objects created by simulate()
        xx <- rep(NA_real_,nrow(model.frame(object)))
    }
    nm <- rownames(model.frame(object)) %||% seq_along(xx)
    names(xx) <- nm
    if (!is.null(fit.na.action <- attr(model.frame(object),"na.action")))
        napredict(fit.na.action, xx)
    else
        xx
}

##' Extract the fixed-effects estimates
##'
##' Extract the estimates of the fixed-effects parameters from a fitted model.
##' @name fixef
##' @title Extract fixed-effects estimates
##' @aliases fixef fixed.effects fixef.merMod
##' @docType methods
##' @param object any fitted model object from which fixed effects estimates can
##' be extracted.
##' @param noScale logical; if TRUE, returns the non-scaled parameters
##' @param \dots optional additional arguments. Currently none are used in any
##' methods.
##' @return a named, numeric vector of fixed-effects estimates.
##' @keywords models
##' @examples
##' fixef(lmer(Reaction ~ Days + (1|Subject) + (0+Days|Subject), sleepstudy))
##' @importFrom nlme fixef
##' @export fixef
##' @method fixef merMod
##' @export
fixef.merMod <- function(object, add.dropped = FALSE, noScale = NULL, ...) {
    X <- getME(object,"X")
    ff <- structure(object@beta, names = dimnames(X)[[2]])
    
    if(is.null(noScale) || (!is.null(noScale) && !noScale)){
      if (!is.null(sc <- attr(X, "scaled:scale"))) {
        ce <- attr(X, "scaled:center")
        ## modifying intercept
        if ("(Intercept)" %in% names(ff)) {
          intercept_shift <- sum((ff[names(sc)] * ce[names(sc)]) / sc[names(sc)])
          ff[["(Intercept)"]] <- ff[["(Intercept)"]] - intercept_shift
        }
        # modifying beta coefficients
        ff[names(sc)] <- ff[names(sc)] / sc[names(sc)]
      }
    }
    
    if (add.dropped) {
        if (!is.null(dd <- attr(X,"col.dropped"))) {
            ## restore positions dropped for rank deficiency
            vv <- numeric(length(ff)+length(dd))
            all.pos <- seq_along(vv)
            kept.pos <- all.pos[-dd]
            vv[kept.pos] <- ff
            names(vv)[kept.pos] <- names(ff)
            vv[dd] <- NA
            names(vv)[dd] <- names(dd)
            ff <- vv
        }
    }
    return(ff)
}


getFixedFormula <- function(form) {
    RHSForm(form) <- reformulas::nobars(RHSForm(form))
    form
}

##' @importFrom stats formula
##' @S3method formula merMod
formula.merMod <- function(x, fixed.only=FALSE, random.only=FALSE, ...) {
    if (missing(fixed.only) && random.only) fixed.only <- FALSE
    if (fixed.only && random.only) stop("can't specify 'only fixed' and 'only random' terms")
    if (is.null(form <- attr(x@frame,"formula"))) {
        if (!grepl("lmer$",deparse(getCall(x)[[1]])))
            stop("can't find formula stored in model frame or call")
        form <- as.formula(formula(getCall(x),...))
    }
    if (fixed.only) {
        form <- getFixedFormula(form)
    }
    if (random.only) {
        ## from predict.R
        form <- lme4_reOnly(form, response=TRUE)
    }
    form
}

##' @S3method isREML merMod
isREML.merMod <- function(x, ...) as.logical(x@devcomp$dims[["REML"]])

##' @S3method isGLMM merMod
isGLMM.merMod <- function(x,...) {
  as.logical(x@devcomp$dims[["GLMM"]])
  ## or: is(x@resp,"glmResp")
}

##' @S3method isNLMM merMod
isNLMM.merMod <- function(x,...) {
  as.logical(x@devcomp$dims[["NLMM"]])
  ## or: is(x@resp,"nlsResp")
}

##' @S3method isLMM merMod
isLMM.merMod <- function(x,...) {
  !isGLMM(x) && !isNLMM(x)
  ## or: is(x@resp,"lmerResp") ?
}

npar.merMod <- function(object) {
    n <- length(object@beta) + getParLength(object) +
      object@devcomp[["dims"]][["useSc"]]
    ## FIXME: this is a bit of a hack: a user *might* have specified
    ## negative binomial family with a known theta, in which case we
    ## shouldn't count it as extra.  Either glmer.nb needs to set a
    ## flag somewhere, or we need class 'nbglmerMod' to extend 'glmerMod' ...
    ## We do *not* want to use the 'useSc' slot (as above), because
    ## although theta is in some sense a scale parameter, it's not
    ## one in the formal sense (and isn't stored in the 'sigma' slot)
    if (grepl("Negative Binomial",family(object)$family)) {
        n <- n+1
    }
    return(n)
    ## TODO: how do we feel about counting the scale parameter ???
}

##' @importFrom stats logLik
##' @S3method logLik merMod
logLik.merMod <- function(object, REML = NULL, ...) {
    if (is.null(REML) || is.na(REML[1]))
        REML <- isREML(object)
    val <- -devCrit(object, REML = REML)/2
    ## dc <- object@devcomp
    nobs <- nobs.merMod(object)
    structure(val,
              nobs = nobs,
              nall = nobs,
              df = npar.merMod(object),
              ## length(object@beta) + length(object@theta) + dc$dims[["useSc"]],
              class = "logLik")
}

##' @importFrom stats df.residual
##' @S3method df.residual merMod
##  TODO: not clear whether the residual df should be based
##  on p=length(beta) or p=length(c(theta,beta)) ... but
##  this is just to allow things like aods3::gof to work ...
##
df.residual.merMod <- function(object, ...) {
    nobs(object)-npar.merMod(object)
}

##' @importFrom stats logLik
##' @S3method model.frame merMod
model.frame.merMod <- function(formula, fixed.only=FALSE, ...) {
    fr <- formula@frame
    if (fixed.only) {
        vars <- attr(terms(fr),"varnames.fixed")
        if (is.null(vars)) {
            ## back-compatibility: saved objects pre 1.1-15
            ff <- formula(formula,fixed.only=TRUE)
            ## thanks to Thomas Leeper and Roman Lustrik, Stack Overflow
            ## https://stackoverflow.com/questions/18017765/extract-variables-in-formula-from-a-data-frame
            vars <- rownames(attr(terms.formula(ff), "factors"))
        }
        vars <- gsub("`","",vars) ## weirdness in deparsing variable names with spaces
        fr <- fr[vars]
    }
    fr
}

##' @importFrom stats model.matrix
##' @S3method model.matrix merMod
##' @param noScale logical; if TRUE, returns the non-scaled parameters
model.matrix.merMod <-
  function(object, type = c("fixed", "random", "randomListRaw"), 
           noScale = NULL,...) {
    X <- object@pp$X
    # Re-scales back the model matrix on command
    if (!is.null(sc <- attr(object@pp$X, "scaled:scale"))){
      if((is.null(noScale)) || (!is.null(noScale) && !noScale)){
        unscale_cols <- setdiff(colnames(X), "(Intercept)")
        ce <- attr(object@pp$X, "scaled:center")
        X[, unscale_cols] <- sweep(X[, unscale_cols], 2, sc, `*`)
        X[, unscale_cols] <- sweep(X[, unscale_cols], 2, ce, `+`)
      }
    }
    
    switch(type[1],
           "fixed" = X,
           "random" = getME(object, "Z"),
           "randomListRaw" = mmList(object))
  }

##' Dummy variables (experimental)
##'
##' Largely a wrapper for \code{model.matrix} that
##' accepts a factor, \code{f}, and returns a dummy
##' matrix with \code{nlevels(f)-1} columns.
dummy <- function(f, levelsToKeep){
  f <- as.factor(f)
  if (all(is.na(f))) return(rep(NA_real_, length(f)))
  mm <- model.matrix(~ 0 + f)
  colnames(mm) <- levels(f)

                                        # sort out levels to keep
  missingLevels <- missing(levelsToKeep)
  if(missingLevels) levelsToKeep <- levels(f)[-1]
  if(!any(levels(f) %in% levelsToKeep))
      stop("at least some of the levels in f ",
           "must also be present in levelsToKeep")
  if(!all(levelsToKeep %in% levels(f)))
      stop("all of the levelsToKeep must be levels of f")
  mm <- mm[, levelsToKeep, drop=FALSE]

  ##                                       # communicate that some usages are unlikely
  ##                                       # to help with readibility, which is the
  ##                                       # whole purpose of dummy()
  ## if((!missingLevels)&&(ncol(mm) > 1))
  ##     message("note from dummy:  explicitly specifying more than one ",
  ##             "level to keep may do little to improve readibility")
  return(mm)
}


##' @importFrom stats nobs
##' @S3method nobs merMod
nobs.merMod <- function(object, ...) nrow(object@frame)

## used in  summary.merMod():
ngrps <- function(object, ...) UseMethod("ngrps")

ngrps.default <- function(object, ...) stop("Cannot extract the number of groups from this object")

ngrps.merMod <- function(object, ...) vapply(object@flist, nlevels, 1)

ngrps.factor <- function(object, ...) nlevels(object)

##' @importFrom nlme ranef
##' @export ranef
NULL

##' Extract the modes of the random effects
##'
##' A generic function to extract the conditional modes of the random effects
##' from a fitted model object.  For linear mixed models the conditional modes
##' of the random effects are also the conditional means.
##'
##' If grouping factor i has k levels and j random effects per level the ith
##' component of the list returned by \code{ranef} is a data frame with k rows
##' and j columns.  If \code{condVar} is \code{TRUE} the \code{"postVar"}
##' attribute is an array of dimension j by j by k.  The kth face of this array
##' is a positive definite symmetric j by j matrix.  If there is only one
##' grouping factor in the model the variance-covariance matrix for the entire
##' random effects vector, conditional on the estimates of the model parameters
##' and on the data will be block diagonal and this j by j matrix is the kth
##' diagonal block.  With multiple grouping factors the faces of the
##' \code{"postVar"} attributes are still the diagonal blocks of this
##' conditional variance-covariance matrix but the matrix itself is no longer
##' block diagonal.
##' @name ranef
##' @aliases ranef ranef.merMod
##' @param object an object of a class of fitted models with random effects,
##' typically an \code{"\linkS4class{merMod}"} object.
##' @param condVar an optional logical argument indicating if the conditional
##' variance-covariance matrices of the random effects should be added as an attribute.
##' @param postVar a (deprecated) synonym for \code{condVar}
##' @param drop an optional logical argument indicating components of the return
##' value that would be data frames with a single column, usually a column
##' called \sQuote{\code{(Intercept)}}, should be returned as named vectors.
##' @param whichel an optional character vector of names of grouping factors for
##' which the random effects should be returned.  Defaults to all the grouping
##' factors.
##' @param \dots some methods for this generic function require additional
##' arguments.
##' @return A list of data frames, one for each grouping factor for the random
##' effects.  The number of rows in the data frame is the number of levels of
##' the grouping factor.  The number of columns is the dimension of the random
##' effect associated with each level of the factor.
##'
##' If \code{condVar} is \code{TRUE} each of the data frames has an attribute
##' called \code{"postVar"} which is a three-dimensional array with symmetric
##' faces.
##'
##' When \code{drop} is \code{TRUE} any components that would be data frames of
##' a single column are converted to named numeric vectors.
##' @note To produce a \dQuote{caterpillar plot} of the random effects apply
##' \code{\link[lattice:xyplot]{dotplot}} to the result of a call to
##' \code{ranef} with \code{condVar = TRUE}.
##' @examples
##' fm1 <- lmer(Reaction ~ Days + (Days|Subject), sleepstudy)
##' fm2 <- lmer(Reaction ~ Days + (1|Subject) + (0+Days|Subject), sleepstudy)
##' fm3 <- lmer(diameter ~ (1|plate) + (1|sample), Penicillin)
##' ranef(fm1)
##' str(rr1 <- ranef(fm1, condVar = TRUE))
##' dotplot(rr1)  ## default
##' ## specify free scales in order to make Day effects more visible
##' dotplot(rr1,scales = list(x = list(relation = 'free')))[["Subject"]]
##' str(ranef(fm2, condVar = TRUE))
##' op <- options(digits = 4)
##' ranef(fm3, drop = TRUE)
##' options(op)
##' @keywords models methods
##' @method ranef merMod
##' @export
ranef.merMod <- function(object, condVar = TRUE, drop = FALSE,
                         whichel = names(ans), postVar = FALSE, ...)
{

    if (length(L <- list(...))>0) {
        warning(paste("additional arguments to ranef.merMod ignored:",
                      paste(names(L),collapse=", ")))
    }
    if (!missing(postVar) && missing(condVar)) {
        warning(sQuote("postVar")," is deprecated: please use ",
                sQuote("condVar")," instead")
        condVar <- postVar
    }
    ans <- object@pp$b(1) ## not always == c(matrix(unlist(getME(object,"b"))))
    if (!is.null(fl <- object@flist)) {
        ## evaluate the list of matrices
        levs <- lapply(fl, levels)
        asgn <- attr(fl, "assign")
        cnms <- object@cnms
        nc <- lengths(cnms) ## number of terms
        ## nb <- nc * lengths(levs)[asgn] ## number of cond modes per term
        nb <- diff(object@Gp)  ## differencing group index is more robust
        nbseq <- rep.int(seq_along(nb), nb)
        ml <- split(ans, nbseq)
        for (i in seq_along(ml))
            ml[[i]] <- matrix(ml[[i]], ncol = nc[i], byrow = TRUE,
                              dimnames = list(NULL, cnms[[i]]))
        ## create a list of data frames corresponding to factors
        ans <- lapply(seq_along(fl),
                      function(i) {
                                     m <- ml[asgn == i]
                                     b2 <- vapply(m,nrow,numeric(1))
                                     ub2 <- unique(b2)
                                     if (length(ub2)>1)
                                         stop("differing numbers of b per group")
                         ## if number of sets of modes != number of levels (e.g. Gaussian process/phyloglmm),
                         ##   generate numeric sequence for names

                         rnms <- if (ub2==length(levs[[i]])) levs[[i]] else seq(ub2)
                         data.frame(do.call(cbind, m),
                                    row.names = rnms,
                                    check.names = FALSE)
        })
        names(ans) <- names(fl)
                                        # process whichel
        stopifnot(is(whichel, "character"))
        whchL <- names(ans) %in% whichel
        ans <- ans[whchL]

        if (condVar) {
            sigsqr <- sigma(object)^2
            rp <- rePos$new(object)
            if(any(lengths(rp$terms) > 1L)) {
                ## use R machinery here ...
                vv <- arrange.condVar(object,condVar(object, scaled=TRUE))
            } else {
                vv <- .Call(merPredDcondVar, object@pp$ptr(), as.environment(rp))
                vv <- lapply(vv, "*", sigsqr)
            }
            for (i in names(ans)) {
                attr(ans[[i]], "postVar") <- vv[[i]]
            }
        }
        if (drop)
            ans <- lapply(ans, function(el)
                      {
                          if (ncol(el) > 1) return(el)
                          pv <- drop(attr(el, "postVar"))
                          el <- drop(as.matrix(el))
                          if (!is.null(pv))
                              attr(el, "postVar") <- pv
                          el
                      })
        class(ans) <- "ranef.mer"
    }
    ans
}## ranef.merMod

print.ranef.mer <- function(x, ...) {
    print(unclass(x), ...)
    if(any(has.pv <- vapply(x, function(el)
                            !is.null(attr(el, "postVar")), NA)))
        cat('with conditional variances for',
            paste(dQuote(names(x)[has.pv]), sep=", "), "\n")
    invisible(x)
}

## try to redo refit by calling modular structure ...
refit2.merMod <- function(object,
                          newresp=NULL) {
    ## the idea is to steal as much structure as we can from the
    ## previous fit, including
    ##  * starting parameter values
    ##  * random-effects structure
    ##  * fixed-effects structure
    ##  * model frame
    ## and jump into the modular structure at an appropriate place;
    ##  essentially, this should merge with a smart-as-possible
    ##  version of 'update' ...

}

## FIXME DRY: much of copy'n'paste from lmer() etc .. ==> become more modular (?)
refit.merMod <- function(object,
                         newresp = NULL,
                         newweights = NULL,
                         ## formula=NULL, weights=NULL,
                         rename.response = FALSE,
                         maxit = 100L, ...)
{

    if (isNLMM(object))
        stop(gettextf("'%s' does not yet support nonlinear mixed models; try '%s' instead",
                      "refit.merMod", "update.merMod"),
             domain = NA)
    haveGLMM <- isGLMM(object)
    l... <- list(...)

    ctrl.arg <- NULL
    if("control" %in% names(l...)) ctrl.arg <- l...$control

    if(!all(names(l...) %in% c("control", "verbose"))) {
        warning("additional arguments to refit.merMod ignored")
    }
    ## TODO: not clear whether we should reset the names
    ##       to the new response variable.  Maybe not.

    ## retrieve name before it gets mangled by operations on newresp
    newrespSub <- substitute(newresp)

    ## for backward compatibility/functioning of refit(fit,simulate(fit))
    if (is.list(newresp)) {
        if (length(newresp)==1) {
            na.action <- attr(newresp,"na.action")
            newresp <- newresp[[1]]
            attr(newresp,"na.action") <- na.action
        } else {
            stop("refit not implemented for 'newresp' lists of length > 1: ",
                 "consider ", sQuote("lapply(object,refit)"))
        }
    }

    ## oldresp <- object@resp$y # need to set this before deep copy,
    ##                          # otherwise it gets reset with the call
    ##                          # to setResp below

    ## somewhat repeated from profile.merMod, but sufficiently
    ##  different that refactoring is slightly non-trivial
    ## "three minutes' thought would suffice ..."
    control <-
        if (!is.null(ctrl.arg)) {
            if (length(ctrl.arg$optCtrl) == 0) { ## use object's version:
                obj.control <- object@optinfo$control
                ignore.pars <- c("xst", "xt")
                if (any(ign <- names(obj.control) %in% ignore.pars))
                    obj.control <- obj.control[!ign]
                ctrl.arg$optCtrl <- obj.control
            }
            ctrl.arg
        }
        else if (haveGLMM)
            glmerControl()
        else
            lmerControl()
    if (object@optinfo$optimizer == "optimx") {
        control$optCtrl <- object@optinfo$control
    }
    ## we need this stuff defined before we call .glmerLaplace below ...
    pp      <- object@pp$copy()
    dc      <- object@devcomp
    nAGQ    <- if (haveGLMM) dc$dims[["nAGQ"]]
 ## nth     <- dc$dims[["nth"]]
    verbose <- l...$verbose; if (is.null(verbose)) verbose <- 0L
    if (!is.null(newresp)) {
        ## update call and model frame with new response
        rcol <- attr(attr(model.frame(object), "terms"), "response")
        if (rename.response) {
            attr(object@frame,"formula")[[2]] <- object@call$formula[[2]] <-
                newrespSub
            names(object@frame)[rcol] <- deparse(newrespSub)
        }
        if (!is.null(na.act <- attr(object@frame,"na.action")) &&
            is.null(attr(newresp,"na.action"))) {
            ## will only get here if na.action is 'na.omit' or 'na.exclude'
            ## *and* newresp does not have an 'na.action' attribute
            ## indicating that NAs have already been filtered
            newresp <- if (is.matrix(newresp))
                newresp[-na.act, ]
            else newresp[-na.act]
        }
        object@frame[[rcol]] <- newresp
    }

    if (!is.null(newweights)) {
        ## DRY ...
        if (!is.null(na.act <- attr(object@frame,"na.action")) &&
            is.null(attr(newweights, "na.action"))) {
            newweights <- newweights[-na.act]
        }
        object@frame[["(weights)"]] <- newweights
        oc <- attr(attr(object@frame, "terms"), "dataClasses")
        attr(attr(object@frame, "terms"), "dataClasses") <- c(oc, `(weights)` = "numeric")

        object@call$weights <- substitute(newweights)

        ## try to make sure new weights are findable later
        assign(deparse(substitute(newweights)),
               newweights,
               environment(formula(object)))
    }

    rr <-
    if (!haveGLMM)
        mkRespMod(model.frame(object), REML = object@resp$REML)
    else
        mkRespMod(model.frame(object), family = family(object))

    if(!is.null(newresp)) {
        if(family(object)$family == "binomial") {
            ## re-do conversion of two-column matrix and factor
            ##  responses to proportion/weights format
            if (is.matrix(newresp) && ncol(newresp) == 2) {
                ntot <- rowSums(newresp)
                ## FIXME: test what happens for (0,0) rows
                newresp <- newresp[,1]/ntot
                rr$setWeights(ntot)
            }
            if (is.factor(newresp)) {
                ## FIXME: would be better to do this consistently with
                ## whatever machinery is used in glm/glm.fit/glmer  ??
                newresp <- as.numeric(newresp)-1
            }
        }
        ## if (haveGLMM && rr$family$family=="binomial") {

        ## }
        stopifnot(length(newresp <- as.numeric(as.vector(newresp))) ==
                  length(rr$y))

    }

    if (haveGLMM) {
        GQmat <- GHrule(nAGQ)

        if (nAGQ <= 1) {
            glmerPwrssUpdate(pp,rr, control$tolPwrss, GQmat, maxit=maxit)
        } else {
            glmerPwrssUpdate(pp,rr, control$tolPwrss, GQmat, maxit=maxit,
                             grpFac = object@flist[[1]])
        }
    }

    reCovs <- getReCovs(object)
    devlist <-
        c(list(pp = pp,
               resp = rr,
               mkPar = mkMkPar(reCovs),
               mkTheta = mkMkTheta(reCovs)),
        if (haveGLMM) {
            baseOffset <- forceCopy(object@resp$offset)

            list(tolPwrss= dc$cmp [["tolPwrss"]],
                 compDev = dc$dims[["compDev"]],
                 nAGQ = nAGQ,
                 lp0 = pp$linPred(1), ## object@resp$eta - baseOffset,
                 baseOffset = baseOffset,
                 pwrssUpdate = glmerPwrssUpdate,
                 ## save GQmat in the object and use that instead of nAGQ
                 GQmat = GHrule(nAGQ),
                 fac = object@flist[[1]],
                 verbose=verbose,
                 dpars=seq_len(getParLength(object)))
        }
        )
    rho <- list2env(devlist)
    ff <- mkdevfun(rho, nAGQ=nAGQ, maxit=maxit, verbose=verbose)
 ## xst       <- rep.int(0.1, nth)
    x0        <- rho$mkPar(rho$pp$theta)
    lower     <- getLower(object)
    upper     <- getUpper(object)
    if (haveGLMM && nAGQ > 0L) {
     ## xst   <- c(xst, sqrt(diag(pp$unsc())))
        n_beta <- length(fixef(object))
        x0    <- c(x0, unname(fixef(object)))
        lower <- c(lower, rep(-Inf, n_beta))
        upper <- c(upper, rep( Inf, n_beta))
    }
    ## control <- c(control,list(xst=0.2*xst, xt=xst*0.0001))
    ## FIX ME: allow use.last.params to be passed through
    calc.derivs <- !is.null(object@optinfo$derivs)
    ## if (haveGLMM) {
    ##     rho$resp$updateWts()
    ##     rho$pp$updateDecomp()
    ##     rho$lp0 <- rho$pp$linPred(1)
    ## }
    optimizer <- object@optinfo$optimizer
    if (!is.null(newopt <- ctrl.arg$optimizer)) {
        ## we might end up with a length-2 optimizer vector ...
        ##  use the *last* element
        optimizer <- newopt[length(newopt)]
    }
    opt <- optwrap(optimizer,
                   ff, x0, lower=lower, upper=upper,
                   control=control$optCtrl,
                   calc.derivs=calc.derivs)
    cc <- checkConv(attr(opt,"derivs"),opt$par,
                    ## FIXME: was there a reason that ctrl was passed
                    ## via the call slot?  it was causing problems
                    ## when optTheta called refit (github issue #173)
                    # ctrl = eval(object@call$control)$checkConv,
                    ctrl = control$checkConv,
                    lbound=lower, ubound=upper)
    if (haveGLMM) rr$setOffset(baseOffset)
    mkMerMod(environment(ff), opt,
             list(flist=object@flist, cnms=object@cnms,
                  Gp=object@Gp,
                  lower=getLower(object),
                  upper=getUpper(object),
                  reCovs=reCovs),
             object@frame, getCall(object), cc)
}

refitML.merMod <- function (x, optimizer="bobyqa", ...) {
    ## FIXME: optimizer is set to 'bobyqa' for back-compatibility, but that's not
    ##  consistent with lmer (default NM).  Should be based on internally stored 'optimizer' value
    if (!isREML(x)) return(x)
    stopifnot(is(rr <- x@resp, "lmerResp"))
    reCovs <- getReCovs(x)
    rho <- new.env(parent=parent.env(environment()))
    rho$resp <- new(class(rr), y=rr$y, offset=rr$offset, weights=rr$weights, REML=0L)
    xpp <- x@pp$copy()
    rho$pp <- new(class(xpp), X=xpp$X, Zt=xpp$Zt, Lambdat=xpp$Lambdat,
                  Lind=xpp$Lind, theta=xpp$theta, n=nrow(xpp$X))
    rho$mkPar <- mkMkPar(reCovs)
    rho$mkTheta <- mkMkTheta(reCovs)
    devfun <- mkdevfun(rho, 0L) # FIXME? also pass {verbose, maxit, control}
    opt <- ## "smart" calc.derivs rules
        if(optimizer == "bobyqa" && !any("calc.derivs" == ...names()))
            optwrap(optimizer, devfun, rho$mkPar(rho$pp$theta), lower=getLower(x), upper=getUpper(x), calc.derivs=TRUE, ...)
        else
            optwrap(optimizer, devfun, rho$mkPar(rho$pp$theta), lower=getLower(x), upper=getUpper(x), ...)
    ## FIXME: Should be able to call mkMerMod() here, and be done
    n <- length(rr$y)
    pp <- rho$pp
    p <- ncol(pp$X)
    dims <- c(N=n, n=n, p=p, nmp=n-p, q=nrow(pp$Zt), nth=length(pp$theta),
              useSc=1L, reTrms=length(x@cnms),
              spFe=0L, REML=0L, GLMM=0L, NLMM=0L)#, nAGQ=NA_integer_)
    wrss <- rho$resp$wrss()
    ussq <- pp$sqrL(1)
    pwrss <- wrss + ussq
    cmp <- c(ldL2=pp$ldL2(), ldRX2=pp$ldRX2(), wrss=wrss, ussq=ussq,
             pwrss=pwrss, drsum=NA_real_, dev=opt$fval, REML=NA,
             sigmaML=sqrt(pwrss/n), sigmaREML=sqrt(pwrss/(n-p)))
    ## modify the call  to have REML=FALSE. (without evaluating the call!)
    cl <- x@call
    cl[["REML"]] <- FALSE
    ans <-
    new("lmerMod", call = cl, frame=x@frame, flist=x@flist,
        cnms=x@cnms, theta=pp$theta, beta=pp$delb, u=pp$delu,
        optinfo = .optinfo(opt),
        lower=getLower(x), devcomp=list(cmp=cmp, dims=dims), pp=pp, resp=rho$resp,
        Gp=x@Gp)
    attr(ans, "upper") <- getUpper(x)
    attr(ans, "reCovs") <- upReCovs(reCovs, rho$pp$theta)
    ans
}

##' residuals of merMod objects                 --> ../man/residuals.merMod.Rd
##' @param object a fitted [g]lmer (\code{merMod}) object
##' @param type type of residuals
##' @param scaled scale residuals by residual standard deviation (=scale parameter)?
##' @param \dots additional arguments (ignored: for method compatibility)
residuals.merMod <- function(object,
                             type = if(isGLMM(object)) "deviance" else "response",
                             scaled = FALSE, ...) {
    r <- residuals(object@resp, type,...)
    fr <- model.frame(object)
    if (is.null(nm <- rownames(fr))) nm <- seq_along(r)
    names(r) <- nm
    if (scaled) r <- r/sigma(object)
    if (!is.null(na.action <- attr(fr, "na.action")))
        naresid(na.action, r)
    else
        r
}

##' @rdname residuals.merMod
##' @S3method residuals lmResp
##' @method residuals lmResp
residuals.lmResp <- function(object,
                             type = c("working", "response", "deviance",
                                      "pearson", "partial"), ...) {
    y <- object$y
    r <- object$wtres
    mu <- object$mu
    switch(match.arg(type),
           working =,
           response = y-mu,
           deviance =,
           pearson = r,
           partial = stop(gettextf("partial residuals are not implemented yet"),
                          call. = FALSE)
           )
}

##' @rdname residuals.merMod
##' @S3method residuals glmResp
##' @method residuals glmResp
residuals.glmResp <- function(object, type = c("deviance", "pearson",
                                      "working", "response", "partial"),
                              ...) {
    type <- match.arg(type)
    y <- object$y
    mu <- object$mu
    switch(type,
           deviance = {
               ## protect against slightly negative resids
               ## (GH 812)
               d.res <- sqrt(pmax(0,object$devResid()))
               ifelse(y > mu, d.res, -d.res)
           },
           pearson = object$wtres,
           working = object$wrkResids(),
           response = y - mu,
           partial = stop(gettextf("partial residuals are not implemented yet"),
                    call. = FALSE)
       )

}

hatvalues.merMod <- function(model, fullHatMatrix = FALSE, ...) {
    if(isGLMM(model)) warning("the hat matrix may not make sense for GLMMs")
    ## FIXME:  add restriction for NLMMs?

    ## prior weights, W ^ {1/2} :
    ## weights() will restore NA values if na.action = na.exclude,
    ##  need to drop them for now and restore at the end
    sqrtW <- Diagonal(x = na.omit(sqrt(weights(model, type = "prior"))))
    vList <- getME(model, c("L", "Lambdat", "Zt", "RX", "X", "RZX"))
    ## CL:= right factor of the random-effects component of the hat matrix  (64)
    CL <- with(vList,
               solve(L, solve(L,
                              Lambdat %*% Zt %*% sqrtW,
                              system = "P"), system = "L"))
    ## restore dimnames (needed since Matrix 1.5.2)
    dimnames(CL) <- dimnames(vList$Zt)
    ## CR:= right factor of the fixed-effects component of the hat matrix   (65)
    ##      {MM (FIXME Matrix):  t(.) %*% here faster than crossprod()}
    CR <- with(vList,
               solve(t(RX),
                     ## colScale(t(X), sqrtW) - crossprod(RZX, CL)
                     t(X) %*% sqrtW - crossprod(RZX, CL))
                     )
    res <- if(fullHatMatrix) { ## H = (C_L^T C_L + C_R^T C_R)             (63)
               crossprod(CL) + crossprod(CR)
           } else {
               ## diagonal of the hat matrix, diag(H) :
               colSums(CR^2) + colSums(CL^2)
           }
    napredict(attr(model.frame(model),"na.action"), res)
}


###----- Printing etc ----------------------------

## lme4.0, for GLMM had
## 'Generalized linear mixed model fit by the Laplace approximation'
## 'Generalized linear mixed model fit by the adaptive Gaussian Hermite approximation'
## so did *not* mention  "maximum likelihood" at all in the GLMM case


methTitle <- function(dims) { # dims == object@devcomp$dims
    GLMM <- dims[["GLMM"]]
    kind <- switch(1L + GLMM * 2L + dims[["NLMM"]],
                   "Linear", "Nonlinear",
                   "Generalized linear", "Generalized nonlinear")
    paste(kind, "mixed model fit by",
          if(dims[["REML"]]) "REML"
          else paste("maximum likelihood",
                     if(GLMM) {
                         ## TODO? Use shorter wording here, for (new) 'long = FALSE' argument
                         if((nAGQ <- dims[["nAGQ"]]) == 1)
                             "(Laplace Approximation)"
                         else
                             sprintf("(Adaptive Gauss-Hermite Quadrature, nAGQ = %d)",
                                     nAGQ)
                         }))
}


cat.f <- function(...) cat(..., fill = TRUE)

famlink <- function(object, resp = object@resp) {
    if(is(resp, "glmResp"))
        resp$family[c("family", "link")]
    else list(family = NULL, link = NULL)
}

##' @title print method title
##' @param mtit the result of methTitle(obj)
##' @param class typically class(obj)
.prt.methTit <- function(mtit, class) {
    if(nchar(mtit) + 5 + nchar(class) > (w <- getOption("width"))) {
        ## wrap around
        mtit <- strwrap(mtit, width = w - 2, exdent = 2)
        cat(mtit, " [",class,"]", sep = "", fill = TRUE)
    } else ## previous: simple one-liner
        cat(sprintf("%s ['%s']\n", mtit, class))
}

.prt.family <- function(famL) {
    if (!is.null(f <- famL$family)) {
        cat.f(" Family:", f,
              if(!is.null(ll <- famL$link)) paste(" (", ll, ")"))
    }
}

.prt.resids <- function(resids, digits, title = "Scaled residuals:", ...) {
    cat(title,"\n")
    ## FIXME: need testing code
    rq <- setNames(zapsmall(quantile(resids, na.rm=TRUE), digits + 1L),
                   c("Min", "1Q", "Median", "3Q", "Max"))
    print(rq, digits = digits, ...)
    cat("\n")
}

.prt.call <- function(call, long = TRUE) {
    if (!is.null(cc <- call$formula))
        cat.f("Formula:", deparse(cc))
    if (!is.null(cc <- call$data))
        cat.f("   Data:", deparse(cc))
    if (!is.null(cc <- call$weights))
        cat.f("Weights:", deparse(cc))
    if (!is.null(cc <- call$offset))
        cat.f(" Offset:", deparse(cc))
    if (long && length(cc <- call$control) &&
        !identical((dc <- deparse(cc)), "lmerControl()"))
        ## && !identical(eval(cc), lmerControl()))
        cat.f("Control:", dc)
    if (!is.null(cc <- call$subset))
        cat.f(" Subset:", deparse(cc))
}

##' @title Extract Log Likelihood, AIC, and related statics from a Fitted LMM
##' @param object a LMM model fit
##' @param devianceFUN the function to be used for computing the deviance; should not be changed for  \pkg{lme4} created objects.
##' @param chkREML optional logical indicating \code{object} maybe a REML fit.
##' @param devcomp
##' @return a list with components \item{logLik} and \code{AICtab} where the first is \code{\link{logLik(object)}} and \code{AICtab} is a "table" of AIC, BIC, logLik, deviance, df.residual() values.
llikAIC <- function(object, devianceFUN = devCrit, chkREML = TRUE, devcomp = object@devcomp) {
    llik <- logLik(object)   # returns NA for a REML fit - maybe change?
    AICstats <- {
        if(chkREML && devcomp$dims[["REML"]])
            devcomp$cmp["REML"] # *no* likelihood stats here
        else {
            c(AIC = AIC(llik), BIC = BIC(llik), logLik = c(llik),
              `-2*log(L)` = devianceFUN(object),
              df.resid = df.residual(object))
        }
    }
    list(logLik = llik, AICtab = AICstats)
}

.prt.aictab <- function(aictab, digits = 1) {
    t.4 <- round(aictab, digits)
    if (length(aictab) == 1 && names(aictab) == "REML")
        cat.f("REML criterion at convergence:", t.4)
    else {
        ## slight hack to get residual df formatted as an integer
        t.4F <- format(t.4)
        t.4F["df.resid"] <- format(t.4["df.resid"])
        print(t.4F, quote = FALSE)
    }
}

.prt.VC <- function(varcor, digits,
                    comp = "Std.Dev.",
                    formatter = format, ...) { # '...' *only* passed to print()
    cat("Random effects:\n")
    fVC <- formatVC(varcor, digits=digits, formatter=formatter, comp=comp)
    print(fVC, quote = FALSE, digits = digits, ...)
}

.prt.grps <- function(ngrps, nobs) {
    cat(sprintf("Number of obs: %d, groups: ", nobs),
        paste(paste(names(ngrps), ngrps, sep = ", "), collapse = "; "),
        fill = TRUE)
}

## FIXME: print header ("Warnings:\n") ?
##  change position in output? comes at the very end, could get lost ...
.prt.warn <- function(optinfo, summary=FALSE, ...) {
    if(length(optinfo) == 0) return() # currently, e.g., from refitML()
    ## check all warning slots: print numbers of warnings (if any)
    cc <- optinfo$conv$opt
    msgs <- unlist(optinfo$conv$lme4$messages)
    ## can't put nmsgs/nwarnings compactly into || expression
    ##   because of short-circuiting
    nmsgs <- length(msgs)
    warnings <- optinfo$warnings
    nwarnings <- length(warnings)
    if (cc > 0 || nmsgs > 0 || nwarnings > 0) {
        m <- if (cc==0) {
                 "(OK)"
             } else if (!is.null(optinfo$message)) {
                 sprintf("(%s)",optinfo$message)
             } else ""
        convmsg <- sprintf("optimizer (%s) convergence code: %d %s",
                           optinfo$optimizer, cc, m)
        if (summary) {
            cat(convmsg,sprintf("; %d optimizer warnings; %d lme4 warnings",
                nwarnings,nmsgs),"\n")
        } else {
            cat(convmsg,
                msgs,
                unlist(warnings),
                sep="\n")
            cat("\n")
        }
    }
}

##  options(lme4.summary.cor.max = 20)  --> ./hooks.R
##                                           ~~~~~~~~
## was      .summary.cor.max <- 20    a lme4-namespace hidden global variable

## This is modeled a bit after  print.summary.lm :
## Prints *both*  'mer' and 'merenv' - as it uses summary(x) mainly
##' @S3method print summary.merMod
print.summary.merMod <- function(x, digits = max(3, getOption("digits") - 3),
                                 correlation = NULL, symbolic.cor = FALSE,
                                 signif.stars = getOption("show.signif.stars"),
                                 ranef.comp = c("Variance", "Std.Dev."),
                                 ranef.corr = any(ranef.comp == "Std.Dev."),
                                 show.resids = TRUE, ...)
{
    .prt.methTit(x$methTitle, x$objClass)
    .prt.family(x)
    .prt.call(x$call); cat("\n")
    .prt.aictab(x$AICtab); cat("\n")
    if (show.resids)
        ## need residuals.merMod() rather than residuals():
        ##  summary.merMod has no residuals method
        .prt.resids(x$residuals, digits = digits)
    .prt.VC(x$varcor, digits = digits, useScale = x$useScale,
            comp = ranef.comp,
            corr = ranef.corr, ...)
    .prt.grps(x$ngrps, nobs = x$devcomp$dims[["n"]])

    p <- nrow(x$coefficients)
    if (p > 0) {
        cat("\nFixed effects:\n")
        printCoefmat(x$coefficients, # too radical: zap.ind = 3, #, tst.ind = 4
                     digits = digits, signif.stars = signif.stars)
        ## do not show correlation when   summary(*, correlation=FALSE)  was used:
        hasCor <- !is.null(VC <- x$vcov) && !is.null(VC@factors$correlation)
        ## FIXME: don't understand the logic here. We can easily
        ## defend against the problem of missing pre-computed correlation
        ## function by reconstituting it if necessary
        ## (e.g. if using merDeriv::vcov.lmerMod), as in commented code below.
        ## However, we currently have a test (using fit_agridat_archbold,
        ## see test-methods.R) that fails if we 'fix' this problem ...
        ##
        ## if (hasCor && is.null(VC@factors$correlation)) {
        ##     ## defend against merDeriv definition of vcov.lmerMod; reconstruct
        ##     cc <- cov2cor(VC)
        ##     dimnames(cc) <- dimnames(VC) ## Matrix 1.5.2 bug
        ##     VC@factors <- c(VC@factors, list(correlation = cc))
        ## }
        if(is.null(correlation)) { # default
            cor.max <- getOption("lme4.summary.cor.max")
            correlation <- hasCor && (isTRUE(x$corrSet) || p <= cor.max)
            if(!correlation && p > cor.max && is.na(x$corrSet)) {
                nam <- deparse(substitute(x))
                if(length(nam) > 1 || nchar(nam) >= 32) nam <- "...."
                message(sprintf(paste(
                    "\nCorrelation matrix not shown by default, as p = %d > %d.",
                    "Use print(%s, correlation=TRUE)  or",
                    "    vcov(%s)        if you need it\n", sep = "\n"),
                                p, cor.max, nam, nam))
            }
        }
        else if(!is.logical(correlation)) stop("'correlation' must be NULL or logical")
        if(correlation) {
            if(is.null(VC)) VC <- vcov(x, correlation = TRUE)
            corF <- VC@factors$correlation
            if (is.null(corF)) { # can this still happen?
                message("\nCorrelation of fixed effects could have been required in summary()")
                corF <- cov2cor(VC)
            }
            p <- ncol(corF)
            if (p > 1) {
                rn <- rownames(x$coefficients)
                rns <- abbreviate(rn, minlength = 11)
                cat("\nCorrelation of Fixed Effects:\n")
                if (is.logical(symbolic.cor) && symbolic.cor) {
                    corf <- as(corF, "matrix")
                    dimnames(corf) <- list(rns,
                                           abbreviate(rn, minlength = 1, strict = TRUE))
                    print(symnum(corf))
                } else {
                    corf <- matrix(format(round(corF@x, 3), nsmall = 3),
                                   ncol = p,
                                   dimnames = list(rns, abbreviate(rn, minlength = 6)))
                    corf[!lower.tri(corf)] <- ""
                    print(corf[-1, -p, drop = FALSE], quote = FALSE)
                } ## !symbolic.cor
            }  ## if (p > 1)
        } ## if (correlation)
    } ## if (p>0)

    if(length(x$fitMsgs) && any(nchar(x$fitMsgs) > 0)) {
        cat("fit warnings:\n"); writeLines(x$fitMsgs)
    }
    .prt.warn(x$optinfo,summary=FALSE)
    invisible(x)
}## print.summary.merMod


##' @S3method print merMod
print.merMod <- function(x, digits = max(3, getOption("digits") - 3),
                         correlation = NULL, symbolic.cor = FALSE,
                         signif.stars = getOption("show.signif.stars"),
                         ranef.comp = "Std.Dev.",
                         ranef.corr = any(ranef.comp == "Std.Dev."), ...)
{
    dims <- x@devcomp$dims
    .prt.methTit(methTitle(dims), class(x))
    .prt.family(famlink(x, resp = x@resp))
    .prt.call(x@call, long = FALSE)
    ## useScale <- as.logical(dims[["useSc"]])

    llAIC <- llikAIC(x)
    .prt.aictab(llAIC$AICtab, 4)
    varcor <- VarCorr(x)
    .prt.VC(varcor, digits = digits, comp = ranef.comp, corr = ranef.corr, ...)
    ngrps <- vapply(x@flist, nlevels, 0L)
    .prt.grps(ngrps, nobs = dims[["n"]])
    if(length(cf <- fixef(x)) > 0) {
        cat("Fixed Effects:\n")
        print.default(format(cf, digits = digits),
                      print.gap = 2L, quote = FALSE, ...)
    } else cat("No fixed effect coefficients\n")

    fitMsgs <- .merMod.msgs(x)
    if(any(nchar(fitMsgs) > 0)) {
        cat("fit warnings:\n"); writeLines(fitMsgs)
    }
    .prt.warn(x@optinfo,summary=TRUE)
    invisible(x)
}

##' @exportMethod show
setMethod("show",  "merMod", function(object) print.merMod(object))

##' Return the deviance component list
devcomp <- function(x) {
    .Deprecated("getME(., \"devcomp\")")
    stopifnot(is(x, "merMod"))
    x@devcomp
}

##' @exportMethod getL
setMethod("getL", "merMod", function(x) {
    .Deprecated("getME(., \"L\")")
    getME(x, "L")
})

##' used by tnames
mkPfun <- function(diag.only = FALSE, old = TRUE, prefix = NULL){
    local({
        function(g,e) {
            mm <- outer(e,e,paste,sep = ".")
            if(old) {
                diag(mm) <- e
            } else {
                mm[] <- paste(mm,g,sep = "|")
                if (!is.null(prefix)) mm[] <- paste(prefix[2],mm,sep = "_")
                diag(mm) <- paste(e,g,sep = "|")
                if (!is.null(prefix))  diag(mm) <- paste(prefix[1],diag(mm),sep = "_")
            }
            mm <- if (diag.only) diag(mm) else mm[lower.tri(mm,diag = TRUE)]
            if(old) paste(g,mm,sep = ".") else mm
        }
    })
}


## -> ../man/getME.Rd
getME <- function(object, name, ...) UseMethod("getME")


##' Extract or Get Generalized Components from a Fitted Mixed Effects Model
getME.merMod <- function(object,
                  name = c("X", "Z","Zt", "Ztlist", "mmList",
                  "y", "mu", "u", "b",
                  "Gp", "Tp",
                  "L", "Lambda", "Lambdat", "Lind", "Tlist", "A",
                  "RX", "RZX", "sigma",
                  "flist",
                  "fixef", "beta", "theta", "ST", "par",
                  "REML", "is_REML",
                  "n_rtrms", "n_rfacs",
                  "N", "n", "p", "q", "npar",
                  "p_i", "l_i", "q_i", "k", "m_i", "m",
                  "cnms",
                  "devcomp", "offset", "lower",
                  "devfun", "devarg",
                  "glmer.nb.theta"
                  ), ...)
{
    if(missing(name)) stop("'name' must not be missing")
    ## Deal with multiple names -- "FIXME" is inefficiently redoing things
    if (length(name <- as.character(name)) > 1) {
        names(name) <- name
        return(lapply(name, getME, object = object))
    }
    if(name == "ALL") ## recursively get all provided components
        return(sapply(eval(formals()$name),
                      getME.merMod, object=object, simplify=FALSE))

    stopifnot(is(object,"merMod"))
    name <- match.arg(name)
    rsp  <- object@resp
    PR   <- object@pp
    dc   <- object@devcomp
    th   <- object@theta
    cnms <- object@cnms
    dims <- dc $ dims
    Tpfun <- function(cnms) {
        ltsize <- function(n) n*(n+1)/2 # lower triangle size
        cLen <- cumsum(ltsize(lengths(cnms)))
        setNames(c(0, cLen),
                 c("beg__", names(cnms))) ## such that diff( Tp ) is well-named
    }
    ## mmList. <- mmList(object)
    if(any(name == c("p_i", "q_i", "m_i")))
        p_i <- vapply(mmList(object), ncol, 1L)
    if(any(name == c("l_i", "q_i")))
        l_i <- vapply(object@flist, nlevels, 1L)
    switch(name,
           "X" = PR$X, ## ok ? - check -- use model.matrix() method instead?
           "Z" = t(PR$Zt),
           "Zt" = PR$Zt,
           "Ztlist" =
           {
               getInds <- function(i) {
                   n <- diff(object@Gp)[i] ## number of elements in this block
                   nt <- length(cnms[[i]]) ## number of REs
                   inds <- lapply(seq(nt), seq, to = n, by = nt)  ## pull out individual RE indices
                   inds <- lapply(inds,function(x) x + object@Gp[i])  ## add group offset
               }
               inds <- do.call(c,lapply(seq_along(cnms),getInds))
               setNames(lapply(inds,function(i) PR$Zt[i,]),
                        unlist(getVCNames(object)$vcomp, FALSE, FALSE))
           },
           "mmList" = mmList.merMod(object),
           "y" = rsp$y,
           "mu" = rsp$mu,
           "u" = object@u,
           "b" = crossprod(PR$Lambdat, object@u), # == Lambda %*% u
           "L" = PR$ L(),
           "Lambda" = t(PR$ Lambdat),
           "Lambdat" = PR$ Lambdat,
           "A" = PR$Lambdat %*% PR$Zt,
           "Lind" = PR$ Lind,
           "RX" = structure(PR$RX(), dimnames = list(colnames(PR$X), colnames(PR$X))), ## maybe add names elsewhere?
           "RZX" = structure(PR$RZX, dimnames = list(NULL, colnames(PR$X))), ## maybe add names elsewhere?
           "sigma" = sigma(object),
           "Gp" = object@Gp,
           "Tp" = Tpfun(cnms), # "term-wise theta pointer"
           "flist" = object@flist,
           "fixef" = fixef(object),
           "beta"  = object@beta,
           "theta" = setNames(th, getThetaNames(object)),
           "par" = setNames(object@optinfo$val[seq_len(getParLength(object))],
                            getParNames(object)),
           "ST" = setNames(vec2STlist(object@theta, n = lengths(cnms)),
                           names(cnms)), ## FIXME: check for flexSigma?
           "Tlist" = setNames(lapply(getReCovs(object), getLambda), names(object@cnms)),
           "REML" = dims[["REML"]],
           "is_REML" = isREML(object),
           ## number of random-effects terms
           "n_rtrms" = length(cnms),
           ## number of random-effects grouping factors
           "n_rfacs" = length(object@flist),
           "N" = dims[["N"]],
           "n" = dims[["n"]],
           "p" = dims[["p"]],
           "q" = dims[["q"]],
           "npar" = as.list(dims)[["npar"]] %||% length(getLower(object)),
           "p_i" = p_i,
           "l_i" = l_i,
           "q_i" = p_i * l_i,
           "k" = length(cnms),
           "m_i" = choose(p_i + 1, 2),
           "m" = dims[["nth"]],
           "cnms" = cnms,
           "devcomp" = dc,
           "offset" = rsp$offset,
           "lower" = setNames(getLower(object), getParNames(object)),
           "devfun" = {
               verbose <- getCall(object)$verbose; if (is.null(verbose)) verbose <- 0L
               reCovs <- getReCovs(object)
               if (isGLMM(object)) {
                   reTrms <- getME(object,c("Zt","theta","Lambdat","Lind","flist","cnms"))
                   reTrms$lower <- getLower(object)
                   reTrms$upper <- getUpper(object)
                   reTrms$reCovs <- reCovs
                   mkGlmerDevfun(object@frame, X=getME(object, "X"),
                                 reTrms=reTrms, family=family(object),
                                 nAGQ=object@devcomp$dims[["nAGQ"]],
                                 verbose=verbose,
                                 control=glmerControl(
                                     compDev=as.logical(object@devcomp$dims[["compDev"]]),
                                     tolPwrss=object@devcomp$cmp[["tolPwrss"]]))
               } else {
                   ## copied from refit ... DRY ...
                   devlist <- list(pp=PR, resp=rsp,
                                   lower = getLower(object),
                                   upper = getUpper(object),
                                   mkPar = mkMkPar(reCovs),
                                   mkTheta = mkMkTheta(reCovs))
                   mkdevfun(rho=list2env(devlist),
                            ## FIXME: fragile ... // also pass 'maxit' ?
                            verbose=verbose, control=object@optinfo$control)
               }
           },
           "devarg" = {
             nm <- getParNames(object)
             if (!isLMM(object)) nm <- c(nm, names(fixef(object)))
             setNames(object@optinfo$val, nm)
           },
           ## FIXME: current version gives lower bounds for theta parameters only:
           ## -- these must be extended for [GN]LMMs -- give extended value including -Inf values for beta values?

           "glmer.nb.theta" = if(isGLMM(object) && isNBfamily(rsp$family$family))
               getNBdisp(object) else NA_real_,

           "..foo.." = # placeholder!
           stop(gettextf("'%s' is not implemented yet",
                         sprintf("getME(*, \"%s\")", name))),
           ## otherwise
           stop(sprintf("Mixed-Effects extraction of '%s' is not available for class \"%s\"",
                        name, class(object))))
}## {getME}

##' @importMethodsFrom Matrix t %*% crossprod diag tcrossprod
##' @importClassesFrom Matrix dgCMatrix dpoMatrix corMatrix
NULL

## Extract the conditional variance-covariance matrix of the fixed-effects
## parameters
vcov.merMod <- function(object, correlation = TRUE, sigm = sigma(object),
                        use.hessian = NULL, full = FALSE, noScale = NULL, ...)
{
    ## FIXME: warn/message if GLMM (RX-computation is approximate),
    ## if other vars are specified?
    if (full) return(vcov_full(object, sigm))

    hess.avail <-
         ## (1) numerical Hessian computed?
        (!is.null(h <- object@optinfo$derivs$Hessian) &&
         ## (2) does Hessian include fixed-effect parameters?
         nrow(h) > (ntheta <- length(object@theta)))
    if (is.null(use.hessian)) use.hessian <- hess.avail
    if (use.hessian && !hess.avail) stop(shQuote("use.hessian"),
                                         "=TRUE specified, ",
                                         "but Hessian is unavailable")
    calc.vcov.hess <- function(h) {
        ## invert 2*Hessian, catching errors and forcing symmetric result
        ## ~= forceSymmetric(solve(h/2)[i,i]) : solve(h/2) = 2*solve(h)
        h <- tryCatch(solve(h),
                      error=function(e) matrix(NA_real_,nrow=nrow(h),ncol=ncol(h)))
        i <- -seq_len(ntheta)  ## drop var-cov parameters
        h <- h[i,i]
        forceSymmetric(h + t(h))
    }

    ## alternately: calculate var-cov from implicit (RX) information
    ## provided by fit (always the case for lmerMods)
    V <- sigm^2 * object@pp$unsc()

    if (hess.avail) {
        V.hess <- calc.vcov.hess(h)
        bad.V.hess <- any(is.na(V.hess))
        if (!bad.V.hess) {
            ## another 'bad var-cov' check: positive definite?
            e.hess <- eigen(V.hess,symmetric = TRUE,only.values = TRUE)$values
            if (min(e.hess) <= 0) bad.V.hess <- TRUE
        }
    }
    if (!use.hessian && hess.avail) {
        ## if hessian is available, go ahead and check
        ## for similarity with the RX-based estimate
        var.hess.tol <- 1e-4 # FIXME: should var.hess.tol be user controlled?
        if (!bad.V.hess && any(abs(V-V.hess) > var.hess.tol * V.hess))
            warning("variance-covariance matrix computed ",
                    "from finite-difference Hessian\nand ",
                    "from RX differ by >",var.hess.tol,": ",
                    "consider ",shQuote("use.hessian=TRUE"))
    }
    if (use.hessian) {
        if (!bad.V.hess) {
            V <- V.hess
        } else {
            warning("variance-covariance matrix computed ",
                    "from finite-difference Hessian is\n",
                    "not positive definite or contains NA values: falling back to ",
                    "var-cov estimated from RX")
        }
    }

    ## FIXME: try to catch non-PD matrices
    rr <- tryCatch(as(V, "dpoMatrix"), error = function(e)e)
    if (inherits(rr, "error")) {
        warning(gettextf("Computed variance-covariance matrix problem: %s;\nreturning NA matrix",
                         rr$message), domain = NA)
        rr <- matrix(NA_real_,nrow(V),ncol(V))
    }

    nmsX <- colnames(object@pp$X)
    dimnames(rr) <- list(nmsX,nmsX)

    if(correlation)
        rr@factors$correlation <-
            if(!is.na(sigm)) as(rr, "corMatrix") else rr # (is NA anyway)
    
    ## If auto-scaling is enabled
    if(is.null(noScale) || (!is.null(noScale) && !noScale)){
      if (!is.null(sc <- attr(object@pp$X, "scaled:scale"))) {
        ce <- attr(object@pp$X, "scaled:center")
        
        rr <- scale_vcov(rr, sc, ce)
      }
    }
    rr
}

vcov_full <- function(object, s = sigma(object)) {
    L <- getME(object, "L")
    RX <- getME(object, "RX")
    RZX <- getME(object, "RZX")
    Lambdat <- getME(object, "Lambdat")

    RXtinv <- solve(t(RX))
    LinvLambdat <- solve(L, Lambdat, system = "L")
    Minv <- s * rbind(
                    cbind(LinvLambdat,
                          Matrix(0, nrow = nrow(L), ncol = ncol(RX))),
                    cbind(-RXtinv %*% t(RZX) %*% LinvLambdat, RXtinv)
                )
    Cmat <- crossprod(Minv)

    ## do we have machinery elsewhere for this (names for b-vector) ?
    ## should this be extracted into a utility f'n (e.g. for getME(., "b") ?
    fix_nms <- colnames(object@pp$X)
    rr <- ranef(object, condVar = FALSE)

    gnms <- function(x) c(outer(colnames(x), rownames(x), function(x,y) paste(y, x, sep = ".")))
    rnms <- lapply(rr, gnms)
    re_nms <- unlist(Map(function(n, r) paste(n, r, sep = "."), names(rr), rnms))

    all_nms <- unname(c(re_nms, fix_nms))
    dimnames(Cmat) <- list(all_nms, all_nms)
    return(Cmat)
}

##' @importFrom stats vcov
##' @S3method vcov summary.merMod
vcov.summary.merMod <- function(object, ...) {
    if(is.null(object$vcov)) stop("logic error in summary of merMod object")
    object$vcov
}

if(FALSE)## *NOWHERE* used _FIXME_ ??
## Compute standard errors of fixed effects from an merMod object
##
## @title Standard errors of fixed effects
## @param object "merMod" object,
## @param ... additional, optional arguments.  None are used at present.
## @return numeric vector of length length(fixef(.))
unscaledVar <- function(object, ...) {
    stopifnot(is(object, "merMod"))
    sigma(object) * diag(object@pp$unsc())
}


##' @S3method summary merMod
summary.merMod <- function(object,
                           correlation = (p <= getOption("lme4.summary.cor.max")),
                           use.hessian = NULL,
                           ...)
{
    if (...length() > 0) {
        ## FIXME: need testing code
        warning("additional arguments ignored")
    }
    ## se.calc:
    hess.avail <- (!is.null(h <- object@optinfo$derivs$Hessian) &&
                   nrow(h) > length(object@theta))
    if (is.null(use.hessian)) use.hessian <- hess.avail
    if (use.hessian && !hess.avail)
        stop("'use.hessian=TRUE' specified, but Hessian is unavailable")
    resp <- object@resp
    devC <- object@devcomp
    dd <- devC$dims
    ## cmp <- devC$cmp
    useSc <- as.logical(dd[["useSc"]])
    sig <- sigma(object)
    ## REML <- isREML(object)

    famL <- famlink(resp = resp)
    p <- length(coefs <- fixef(object))

    ## protect against merDeriv's vcov.glmerMod(), which only
    ## handles binomial and Poisson values
    if (is(object, "glmerMod") && !family(object)$family %in% c("binomial", "poisson")) {
        vcov <- vcov.merMod
    }
    vc <- vcov(object, use.hessian = use.hessian)
    stdError <- sqrt(diag(vc))
    coefs <- cbind("Estimate" = coefs,
                   "Std. Error" = stdError)
    if (p > 0) {
        coefs <- cbind(coefs, (cf3 <- coefs[,1]/coefs[,2]), deparse.level = 0)
        colnames(coefs)[3] <- paste(if(useSc) "t" else "z", "value")
        if (isGLMM(object)) # FIXME: if "t" above, cannot have "z" here
            coefs <- cbind(coefs, "Pr(>|z|)" =
                           2*pnorm(abs(cf3), lower.tail = FALSE))
    }

    llAIC <- llikAIC(object)
    ## FIXME: You can't count on object@re@flist,
    ##        nor compute VarCorr() unless is(re, "reTrms"):
    varcor <- VarCorr(object)
                                        # use S3 class for now
    structure(list(methTitle = methTitle(dd),
                   objClass = class(object),
                   devcomp = devC,
                   isLmer = is(resp, "lmerResp"),
                   useScale = useSc,
                   logLik = llAIC[["logLik"]],
                   family = famL$family,
                   link = famL$link,
                   ngrps = ngrps(object),
                   coefficients = coefs,
                   sigma = sig,
                   ## explicitly call method to avoid getting m
                   ## messed up by merDeriv's vcov method
                   ##  (which doesn't assign a VC attribute)
                   vcov = vcov.merMod(object, correlation = correlation, sigm = sig),
                   varcor = varcor, # and use formatVC(.) for printing.
                   AICtab = llAIC[["AICtab"]],
                   call = object@call,
                   residuals = residuals(object,"pearson",scaled = TRUE),
                   fitMsgs = .merMod.msgs(object),
                   optinfo = object@optinfo,
                   ## put corrSet **last** so we don't mess up people relying on numeric indexing
                   ##  of elements (!!)
                   corrSet = if(!missing(correlation)) correlation else NA # TRUE/FALSE (when set) / NA
                   ), class = "summary.merMod")
}

## TODO: refactor? Why the hell do we need a summary method for summary.merMod?
##' @S3method summary summary.merMod
summary.summary.merMod <- function(object, varcov = TRUE, ...) {
    if(varcov && is.null(object$vcov))
        object$vcov <- vcov.merMod(object, correlation = TRUE, sigm = object$sigma)
    object
}


##' @importFrom stats weights
##' @S3method weights merMod
weights.merMod <- function(object, type = c("prior","working"), ...) {
    type <- match.arg(type)
    res <- if (type == "prior") object@resp$weights else object@pp$Xwts^2
    res <- napredict(na.action(object), res)

    ## the working weights available through pp$Xwts should be
    ## equivalent to:
    ##     object@resp$weights*(object@resp$muEta()^2)/object@resp$variance()
    ## however, the unit tests in tests/glmmWeights.R suggest that this
    ## equivalence is approximate.  this may be fine, however, if the
    ## discrepancy is due to another instance of the general problem of
    ## reference class fields not being updated at the optimum, then this
    ## could cause real problems.  see for example:
    ## https://github.com/lme4/lme4/issues/166
    
    return(res)
}

## utility function: x is a ranef.mer object, nx is the name of an element
asDf0 <- function(x,nx,id=FALSE) {
    xt <- x[[nx]]
    ss <- stack(xt)
    ss$ind <- factor(as.character(ss$ind), levels = colnames(xt))
    ss$.nn <- rep.int(reorder(factor(rownames(xt)), xt[[1]],
                              FUN = mean,sort = sort), ncol(xt))
    ## allow 'postVar' *or* 'condVar' names
    pv <- attr(xt,"postVar")
    if (is.null(pv)) {
        pv <- attr(xt,"condVar")
    }
    if (!is.null(pv)) {
        tmpfun <- function(pvi) {
            unlist(lapply(1:nrow(pvi), function(i) sqrt(pvi[i, i, ])))
        }
        if (!is.list(pv)) {
            ss$se <- tmpfun(pv)
        } else {
            ## rely on ordering when unpacking!
            ss$se <- unlist(lapply(pv,tmpfun))
        }
    }
    if (id) ss$id <- nx
    return(ss)
}

## convert ranef object to a long-format data frame, e.g. suitable
##  for ggplot2 (or homemade lattice plots)
## FIXME: have some gymnastics to do if terms, levels are different
##  for different grouping variables - want to maintain ordering
##  but still allow rbind()ing
as.data.frame.ranef.mer <- function(x, ...) {
    xL <- lapply(names(x), asDf0, x=x, id=TRUE)
    ## combine
    xD <- do.call(rbind,xL)
    ## rename ...
    oldnames <- c("values", "ind", ".nn", "se",    "id")
    newnames <- c("condval","term","grp", "condsd","grpvar")
    names(xD) <- newnames[match(names(xD),oldnames)]
    ## reorder ...
    neworder <- c("grpvar","term","grp","condval")
    if ("condsd" %in% names(xD)) neworder <- c(neworder,"condsd")
    xD[neworder]
}

dim.merMod <- function(x) {
    getME(x, c("n", "p", "q", "p_i", "l_i", "q_i", "k", "m_i", "m"))
}

## Internal utility, only used in optwrap() :
##' @title Get the optimizer function and check it minimally
##' @param optimizer character string ( = function name) *or* function
getOptfun <- function(optimizer) {
    if (((is.character(optimizer) && optimizer == "optimx") ||
         deparse(substitute(optimizer)) == "optimx")) {
        if (!requireNamespace("optimx")) {
            stop(shQuote("optimx")," package must be installed order to ",
                 "use ",shQuote('optimizer="optimx"'))
        }
        optfun <- optimx::optimx
    } else if (is.character(optimizer)) {
        optfun <- tryCatch(get(optimizer), error = function(e) NULL)
    } else optfun <- optimizer
    if (is.null(optfun)) stop("couldn't find optimizer function ",optimizer)
    if (!is.function(optfun)) stop("non-function specified as optimizer")
    needArgs <- c("fn","par","lower","control")
    if (anyNA(match(needArgs, names(formals(optfun)))))
        stop("optimizer function must use (at least) formal parameters ",
             paste(sQuote(needArgs), collapse = ", "))
    optfun
}

optwrap <- function(optimizer, fn, par, lower = -Inf, upper = Inf,
                    control = list(), adj = FALSE, calc.derivs = TRUE,
                    force.calc.derivs = FALSE,
                    use.last.params = FALSE,
                    verbose = 0L)
{
    ## calc.derivs may be passed as NULL by some upstream pkgs ...
    calc.derivs <- calc.derivs %||% TRUE
    ## control must be specified if adj==TRUE;
    ##  otherwise this is a fairly simple wrapper
    optfun <- getOptfun(optimizer)
    optName <- if(is.character(optimizer)) optimizer
    else ## "good try":
        deparse(substitute(optimizer))[[1L]]

    lower <- rep_len(lower, length(par))
    upper <- rep_len(upper, length(par))

    if (adj)
        ## control parameter tweaks: only for second round in nlmer, glmer
        switch(optName,
               "bobyqa" = {
                   if(!is.numeric(control$rhobeg)) control$rhobeg <- 0.0002
                   if(!is.numeric(control$rhoend)) control$rhoend <- 2e-7
               },
               "Nelder_Mead" = {
                   if (is.null(control$xst))  {
                       betaSD <- sqrt(diag(environment(fn)$pp$unsc()))
                       xst0 <- rep(0.1, length(par) - length(betaSD))
                       xst1 <- pmin(betaSD, 10)
                       control$xst <- 0.2 * c(xst0, xst1)
                   }
                   if (is.null(control$xt)) control$xt <- control$xst*5e-4
               })
    switch(optName,
           "bobyqa" = {
               if(all(par == 0)) par[] <- 0.001  ## minor kludge
               if(!is.numeric(control$iprint)) control$iprint <- min(verbose, 3L)
           },
           "Nelder_Mead" = control$verbose <- verbose,
           "nloptwrap" = control$print_level <- min(as.numeric(verbose),3L),
           ## otherwise:
           if(verbose) warning(gettextf(
               "'verbose' not yet passed to optimizer '%s'; consider fixing optwrap()",
                                        optName), domain = NA)
           )
    arglist <- list(fn = fn, par = par, lower = lower, upper = upper, control = control)
    ## optimx: must pass method in control (?) because 'method' was previously
    ## used in lme4 to specify REML vs ML
    if (optName == "optimx") {
        if (is.null(method <- control$method))
            stop("must specify 'method' explicitly for optimx")
        arglist$control$method <- NULL
        arglist <- c(arglist, list(method = method))
    }
    ## FIXME: test!  effects of multiple warnings??
    ## may not need to catch warnings after all??
    curWarnings <- list()
    opt <- withCallingHandlers(do.call(optfun, arglist),
                               warning = function(w) {
                                   curWarnings <<- append(curWarnings,list(w$message))
                               })
    ## cat("***",unlist(tail(curWarnings,1)))
    ## FIXME: set code to warn on convergence !=0
    ## post-fit tweaking
    if (optName == "bobyqa") {
        opt$convergence <- opt$ierr
    } else if (optName == "Nelder_Mead") {
        ## fix-up: Nelder_Mead treats running out of iterations as "convergence" (!?)
        if (opt$NM.result==4) opt$convergence <- 4
    } else if (optName == "optimx") {
        opt <- list(par = coef(opt)[1,],
                    fvalues = opt$value[1],
                    method = method,
                    conv = opt$convcode[1],
                    feval = opt$fevals + opt$gevals,
                    message = attr(opt,"details")[,"message"][[1]])
    }
    if ((optconv <- getConv(opt)) != 0) {
        wmsg <- paste("convergence code",optconv,"from",optName)
        if (!is.null(getMsg(opt))) wmsg <- paste0(wmsg,": ",getMsg(opt))
        warning(wmsg)
        curWarnings <<- append(curWarnings,list(wmsg))
    }
    ## pp_before <- environment(fn)$pp
    ## save(pp_before,file="pp_before.RData")

    singular <- min(opt$par - lower, upper - opt$par) < getSingTol()
    if (force.calc.derivs || (calc.derivs && !singular)){
        if (use.last.params) {
            ## +0 tricks R into doing a deep copy ...
            ## otherwise element of ref class changes!
            ## FIXME:: clunky!!
            orig_pars <- opt$par
            orig_theta <- environment(fn)$pp$theta+0
            orig_pars[seq_along(orig_theta)] <- orig_theta
        }
        if (verbose > 10) cat("computing derivatives\n")
        derivs <- deriv12(fn, opt$par, fx = opt$value)
        if (use.last.params) {
            ## run one more evaluation of the function at the optimized
            ##  value, to reset the internal/environment variables in devfun ...
            fn(orig_pars)
        }
    } else derivs <- NULL

    if (!use.last.params) {
        ## run one more evaluation of the function at the optimized
        ##  value, to reset the internal/environment variables in devfun ...
        fn(opt$par)
    }
    structure(opt, ## store all auxiliary information
              optimizer = optimizer,
              control   = control,
              warnings  = curWarnings,
              derivs    = derivs)
}
