#  File src/library/methods/R/trace.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-2016, 2022-2025 The R Core Team
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  https://www.R-project.org/Licenses/

## some temporary (!) hooks to trace the tracing code
.doTraceTrace <- function(on) {
 .assignOverBinding(".traceTraceState", on,
                    environment(.doTraceTrace), FALSE)
  on
}

.traceTraceState <- FALSE

## the internal functions in the evaluator.  These are all prohibited,
## although some of them could just barely be accommodated, with some
## specially designed new definitions (not using ..., for example).
## The gain does not seem worth the inconsistencies; and "if" can
## never be traced, since it has to be used to determine if tracing is
## on.  (see .doTrace())
## The remaining invalid functions create miscellaneous bugs, maybe
## related to the use of "..." as the introduced arguments.  Aside from
## .Call, tracing them seems of marginal value.

.InvalidTracedFunctions <- c("if", "where", "for", "repeat", "(", "{",
                            "next", "break", ".Call", ".Internal", ".Primitive")

.TraceWithMethods <- function(what, tracer = NULL, exit = NULL, at = numeric(),
			      print = TRUE, signature = NULL,
			      where = .GlobalEnv, edit = FALSE, from = NULL,
			      untrace = FALSE, classMethod = FALSE) {
    fromPackage <-
        if(is.function(where)) {
            ## start from the function's environment:  important for
            ## tracing from a namespace
            where <- if(is(where, "genericFunction"))
                parent.env(environment(where))
            else
                environment(where)
            getPackageName(where)
        } else ""
    doEdit <- !isFALSE(edit)
    whereF <- NULL
    pname <- character()
    def <- NULL
    tracingWhere <- "in package"
    refCase <- isS4(where) && (is(where, "envRefClass") ||
                               is(where, "refClassRepresentation"))
    if(refCase) {
        ## some error checking
        if(!is.null(signature))
            stop("argument 'signature' is not meaningful for tracing reference methods")
        .where <- where # to avoid substituting where in the eval() below
        ## A reference class object or its class or its generator
        if(is(.where, "refGeneratorSlot") && !classMethod)
            .where <- .where$def # should now be the refClassRepresentation
        if(is(.where, "refClassRepresentation")) {
            pname <- .where@className
            .where <- .where@refMethods
            tracingWhere <- "for class"
        }
        else {
            tracingWhere <- "for object from class"
            pname <- class(.where)
        }
        ## interpret as tracing .where$what
        def <- eval(substitute(.dollarForEnvRefClass(.where, what)))
        if(!is(def, "refMethodDef")) {
            thisName <- substitute(what)
            stop(gettextf("%s is not a method for reference class %s",
			  sQuote(as.character(if(is.symbol(thisName)) thisName
					      else what)),
                          dQuote(class(where))),
                 domain = NA)
        }
        what <- def@name
        whereF <- .where
    }
    else if(is.function(what)) {
        def <- what
        if(is(def, "genericFunction")) {
            what <- def@generic
            whereF <- .genEnv(what, where)
            pname <- def@package
        }
        else {
            fname <- substitute(what)
            if(is.name(fname)) {
                ## regular case
                what <- as.character(fname)
                temp <- .findFunEnvAndName(what, where)
                whereF <- temp$whereF
                pname <- temp$pname
            }
            else if(is.call(fname) && identical(fname[[1L]], as.name("::"))) {
                whereF <- as.character(fname[[2L]])
                require(whereF, character.only = TRUE)
                whereF <- as.environment(paste0("package:", whereF))
                pname <-  fname[[2L]]
                what <- as.character(fname[[3L]])
            }
            else if(is.call(fname) && identical(fname[[1L]], as.name(":::"))) {
                pname <- paste(fname[[2L]], "(not-exported)")
                whereF <- loadNamespace(as.character(fname[[2L]]))
                what <- as.character(fname[[3L]])
            }
            else
                stop("argument 'what' should be the name of a function")
        }
    }
    else {
        what <- as(what, "character")
        if(length(what) != 1) {
            if (nargs() != 1) {
                if(!is.null(exit)) {
                    if(is.function(exit)) {
                        tname <- substitute(exit)
                        if(is.name(tname))
                            exit <- tname
                        exit <- substitute(TRACE(), list(TRACE=exit))
                    }
                }
                if(!is.null(tracer)) {
                    if(is.function(tracer)) {
                        tname <- substitute(tracer)
                        if(is.name(tname))
                            tracer <- tname
                        tracer <- substitute(TRACE(), list(TRACE=tracer))
                    }
                }
            }
            for(f in what) {
                if(nargs() == 1)
                    trace(f)
                else {
                    Recall(f, tracer, exit, at, print, signature, where, edit, from,
                           untrace, classMethod)
                }
            }
            return(what)
        }
        temp <- .findFunEnvAndName(what, where, signature)
        whereF <- temp$whereF
        pname <- temp$pname
        fname <- what
    }
    if(what %in% .InvalidTracedFunctions)
        stop(gettextf("tracing the internal function %s is not allowed",
		      sQuote(what)), domain = NA)
    if(.traceTraceState) {
        message(".TraceWithMethods: after computing what, whereF", domain = NA)
        browser()
    }
    if(nargs() == 1) # for back compatibility
	return(if(untrace) .primUntrace(what) else .primTrace(what))
## FIXME: for trace(stats:::.....)  we really want -- how can this be solved
##    	return(if(untrace) .primUntrace(frame) else .primTrace(fname))
    if(is.null(whereF)) {
        allWhere <- findFunction(what, where = where)
        if(length(allWhere)==0)
	    stop(gettextf("no function definition for %s found", sQuote(what)),
                 domain = NA)
        whereF <- as.environment(allWhere[[1L]])
    }
    ## detect use with no action specified (old-style R trace())
    if(is.null(tracer) && is.null(exit) && isFALSE(edit))
        tracer <- quote({})
    if(is.null(def))
        def <- getFunction(what, where = whereF)
    if(is(def, "traceable") && isFALSE(edit) && !untrace)
        def <- .untracedFunction(def)
    if(!is.null(signature)) {
        fdef <- if (!is(def, "genericFunction"))
                    getGeneric(as.character(fname), TRUE, where)
                else def
        def <- selectMethod(what, signature, fdef = fdef, optional = TRUE)
        if(isRematched(def)) {
            expr <- substitute(trace(.local, tracer = tr, at = at,
                                     exit = ex,  print = pr,
                                     edit = ed,
                                     where = sys.frame(sys.nframe())),
                               list( tr = substitute(tracer),
                                    ex = exit, at = at, pr = print,
                                    ed = edit))
            at <- 3L
            tracer <- expr
            print <- FALSE
        }
        if(is.null(def)) {
            warning(gettextf("cannot untrace method for %s; no method defined for this signature: %s",
                             sQuote(what),
                             paste(signature, collapse = ", ")),
                    domain = NA)
            return(def)
        }
        ## pick up signature with package slot from selectMethod
        signature <- def@target
    }
    if(untrace) {
        if(.traceTraceState) {
            message(".TraceWithMethods: untrace case", domain = NA)
            browser()
        }

        if(is.null(signature)) {
            ## ensure that the version to assign is untraced
            if(is(def, "traceable")) {
                newFun <- .untracedFunction(def)
            }
            else {
                .primUntrace(what) # to be safe--no way to know if it's traced or not
### or sometimes rather _FIXME_ ?
###             .primUntrace(fname) # (rather than 'what')
                return(what)
            }
        }
        else {
            if(is(def, "traceable"))
                newFun <- .untracedFunction(def)
            else {
                warning(gettextf("the method for %s for this signature was not being traced",
                                 sQuote(what)),
                        domain = NA)
                return(what)
            }
        }
    }
    else {
        if(!is.null(exit)) {
            if(is.function(exit)) {
                tname <- substitute(exit)
                if(is.name(tname))
                    exit <- tname
                exit <- substitute(TRACE(), list(TRACE=exit))
            }
        }
        if(!is.null(tracer)) {
            if(is.function(tracer)) {
                tname <- substitute(tracer)
                if(is.name(tname))
                    tracer <- tname
                tracer <- substitute(TRACE(), list(TRACE=tracer))
            }
        }
        original <- .untracedFunction(def)
        traceClass <- .traceClassName(.class1(original))
        if(is.null(getClassDef(traceClass))) {
            ## e.g. for a simple S3 class name
            traceClass <- .makeTraceClass(traceClass, .class1(original))
### FIXME: allow this to be NULL and then do *not* "think anymore" of S4 classes ...
        }
        if(doEdit && is.environment(edit)) {
            ## trace with the version found in the edit environment
            def <- .findNewDefForTrace(what, signature, edit, fromPackage)
            environment(def) <- environment(original)
            if(is.null(c(tracer, exit))) {
                newFun <- new(traceClass, original)
                newFun@.Data <- def
            }
            else {
                newFun <- new(traceClass, def = def, tracer = tracer, exit = exit, at = at, print = print, doEdit = FALSE)
                newFun@original <- original # left as def by initialize method
            }
            newFun@source <- edit
        }
        else
            newFun <- new(traceClass,
                      def = if(doEdit) def else original, tracer = tracer, exit = exit, at = at,
                      print = print, doEdit = edit)
    }
    global <- identical(whereF, .GlobalEnv)
    if(.traceTraceState) {
        message(".TraceWithMethods: about to assign or setMethod", domain = NA)
        browser()
    }
    if(is.null(signature)) {
        if(bindingIsLocked(what, whereF))
            .assignOverBinding(what, newFun, whereF, global)
        else
            assign(what, newFun, whereF)
        if (length(pname) != 0) {
            ## update the function also in "imports:" environments of already
            ## loaded packages that import package pname

            spname <- sub("^namespace:", "", pname)
                # catching error in case when spname is not a name of a namespace, but
                # e.g. a reference class
            ipkgs <- tryCatch(getNamespaceUsers(spname), error=function(e){c()})
            for(importingPkg in ipkgs) {
              .updateInImportsEnv(what, newFun, importingPkg)
            }
        }
        if(length(grep("[^.]+[.][^.]+", what)) > 0) { #possible S3 method
            ## check for a registered version of the object
            S3MTableName <- ".__S3MethodsTable__."
            if(!is.null(tbl <- get0(S3MTableName, envir = whereF, inherits = FALSE))) {
                if(exists(what, envir = tbl, inherits = FALSE)) {
		    tracedFun <- get(what, envir = whereF, inherits = TRUE)
		    assign(what, tracedFun, envir = tbl)
                }
            }
        }
    }
    else {
        if(untrace && is(newFun, "MethodDefinition") &&
           !identical(newFun@target, newFun@defined))
            ## we promoted an inherited method for tracing, now we have
            ## to remove that method.  Assertion is that there was no directly
            ## specified method, or else defined, target would be identical
            newFun <- NULL
        ## arrange for setMethod to put the new method in the generic
        ## but NOT to assign the methods list object (binding is ignored)
        setMethod(fdef, signature, newFun, where = new.env())
    }
    if(!global) {
        action <- if(untrace)"Untracing" else "Tracing"
        nameSpaceCase <- FALSE
        location <- if(.identC(fromPackage, "")) {
            if(length(pname)==0  && !is.null(whereF))
                pname <- getPackageName(whereF)
            nameSpaceCase <- isNamespace(whereF) &&
            !is.na(match(pname, loadedNamespaces())) &&
            identical(whereF, getNamespace(pname))
            if(length(pname)==0)  # but not possible from getPackagename ?
                ""
            else {
                if(nameSpaceCase)
                    paste0(" in environment <namespace:",  pname, ">")
                else
                    paste0(" ", tracingWhere, " \"",  pname, "\"")
            }
        }
        else paste0(" as seen from package \"", fromPackage, "\"")
	object <- if(refCase) "reference method"
		  else if(is.null(signature)) "function"
		  else "specified method for function"
        object <- paste0(" ", object, " \"", what, "\" ")
        .message(action, object, location)
        ## tracing methods (signature not null) works without setting where
        if(nameSpaceCase && !untrace && is.null(signature) && exists(what, envir = .GlobalEnv)) {
	    untcall <- paste0("untrace(\"", what,
			      "\", where = getNamespace(\"", pname, "\"))")
            .message("Warning: Tracing only in the namespace; to untrace you will need:\n    ",
                     untcall, "\n")
        }
    }
    what
}

.makeTracedFunction <- function(def, tracer, exit, at, print, doEdit) {
    switch(typeof(def),
           builtin = {
               fBody <- substitute({.prim <- DEF; .prim(...)},
                                   list(DEF = def))
               def <- eval(function(...)NULL)
               body(def, envir = .GlobalEnv) <- fBody
           },
           special = {
               fBody <- substitute({do.call(DEF, list(...))},
                                   list(DEF = def))
               def <- eval(function(...)NULL)
               body(def, envir = .GlobalEnv) <- fBody
               warning("making a traced version of a special; arguments may be altered")
           }
           )
    if(!isFALSE(doEdit)) {
        if(is.character(doEdit) || is.function(doEdit)) {
            editor <- doEdit
            doEdit <- TRUE
        }
        else
            editor <- getOption("editor")
    }
    ## look for a request to edit the definition
    if(doEdit) {
        if(is(def, "traceable"))
            def <- as(def, "function") # retain previous tracing if editing
        if(is(editor, "character") && !is.na(match(editor, c("emacs","xemacs")))) {
            ## cater to the usual emacs modes for editing R functions
            file <- tempfile("emacs")
            file <- sub('..$', ".R", file)
        }
        else
            file <- ""
        ## insert any requested automatic tracing expressions before editing
        if(!(is.null(tracer) && is.null(exit) && length(at)==0))
            def <- Recall(def, tracer, exit, at, print, FALSE)
        def2 <- utils::edit(def, editor = editor, file = file)
        if(!is.function(def2))
            stop(gettextf("the editing in trace() can only change the body of the function; got an object of class %s",
                          dQuote(class(def2))),
                 domain = NA)
        if(!identical(args(def), args(def2)))
            stop("the editing in trace() can only change the body of the function, not the arguments or defaults")
        fBody <- body(def2)
    }
    else {
        def <- .untracedFunction(def) # throw away earlier tracing
        fBody <- body(def)
        if(length(at) > 0) {
            if(is.null(tracer))
                stop("cannot use 'at' argument without a trace expression")
            else if(!inherits(fBody, "{"))
                stop("cannot use 'at' argument unless the function body has the form '{ ... }'")
            for(i in at) {
		fBody[[i]] <-
		    if(print)
			substitute({.doTrace(TRACE, MSG); EXPR},
                                   list(TRACE = tracer,
                                        MSG = paste("step",paste(i, collapse=",")),
                                        EXPR = fBody[[i]]))
		    else
			substitute({.doTrace(TRACE); EXPR},
                                   list(TRACE=tracer, EXPR = fBody[[i]]))
            }
        }
        else if(!is.null(tracer)){
	    fBody <-
		if(print)
		    substitute({.doTrace(TRACE, MSG); EXPR},
                                    list(TRACE = tracer, MSG = paste("on entry"), EXPR = fBody))
		else
		    substitute({.doTrace(TRACE); EXPR},
                                    list(TRACE=tracer, EXPR = fBody))
        }
        if(!is.null(exit)) {
	    exit <-
		if(print)
		    substitute(.doTrace(EXPR, MSG),
                                   list(EXPR = exit, MSG = paste("on exit")))
		else
		    substitute(.doTrace(EXPR),
                                   list(EXPR = exit))
            fBody <- substitute({on.exit(TRACE); BODY},
                                list(TRACE=exit, BODY=fBody))
        }
    }
    body(def, envir = environment(def)) <- fBody
    def
}

## return the untraced version of f
.untracedFunction <- function(f) {
    while(is(f, "traceable"))
        f <- f@original
    f
}


.InitTraceFunctions <- function(envir)  {
    setClass("traceable", representation(original = "PossibleMethod", source = "environment"), contains = "VIRTUAL",
             where = envir); clList <- "traceable"
    ## create the traceable classes
    for(cl in c("function", "MethodDefinition", "MethodWithNext", "genericFunction",
                "standardGeneric", "nonstandardGeneric", "groupGenericFunction",
                "derivedDefaultMethod")) {
        .makeTraceClass(.traceClassName(cl), cl, FALSE)
        clList <- c(clList, .traceClassName(cl))
    }
    setClass("sourceEnvironment", contains = "environment",
             representation(packageName = "character", dateCreated = "POSIXt", sourceFile = "character"),
             prototype = prototype( packageName = "", dateCreated = Sys.time(), sourceFile = ""))
    clList <- c(clList, "sourceEnvironment")
    assign(".SealedClasses", c(get(".SealedClasses", envir), clList), envir)
    setMethod("initialize", "traceable",
              function(.Object, ...) .initTraceable(.Object, ...),
              where = envir)
    if(!isGeneric("show", envir))
        setGeneric("show", where = envir, simpleInheritanceOnly = TRUE)
    setMethod("show", "traceable", .showTraceable, where = envir)
    setMethod("show", "sourceEnvironment", .showSource, where = envir)
}

## allow control over whether methods & classes are cached when assigned
## to a particular environment. defaults to TRUE
cacheOnAssign <- function(env) is.null(env$.cacheOnAssign) || env$.cacheOnAssign
setCacheOnAssign <- function(env, onOff = cacheOnAssign(env))
    env$.cacheOnAssign <- if(onOff) TRUE else FALSE

.showTraceable <- function(object) {
    if(identical(object@source, emptyenv())) {
        cat("Object with tracing code, class \"", class(object),
        "\"\nOriginal definition: \n", sep="")
        callGeneric(object@original)
        cat("\n## (to see the tracing code, look at body(object))\n")
    }
    else {
        cat("Object of class \"", class(object),
            "\", from source\n", sep = "")
        callGeneric(object@.Data)
        cat("\n## (to see original from package, look at object@original)\n")
    }
}

.initTraceable <- function(.Object, def, tracer, exit, at, print, doEdit) {
    .Object@source <- emptyenv()
    if(missing(def))
        return(.Object)
    oldClass <- .class1(def)
    oldClassDef <- getClass(oldClass)
    if(!is.null(oldClassDef) && length(oldClassDef@slots) > 0)
        as(.Object, oldClass) <- def # to get other slots in def
    .Object@original <- def
    if(nargs() > 2) {
	if(!is.null(elNamed(getSlots(getClass(class(def))), ".Data")))
	    def <- def@.Data
        .Object@.Data <- .makeTracedFunction(def, tracer, exit, at, print, doEdit)
    }
    .Object
}

.showSource <- function(object) {
    cat("Object of class \"", class(object), "\"\n", sep = "")
    cat("Source environment created ", format(object@dateCreated), "\n")
    if(nzchar(object@packageName))
        cat("For package \"",object@packageName, "\"\n", sep = "")
    if(nzchar(object@sourceFile))
        cat("From source file \"", object@sourceFile, "\"\n", sep = "")
}

.doTracePrint <- function(msg = "") {
    call <- deparse(sys.call(sys.parent(1)))
    if(length(call)>1)
        call <- paste(call[[1L]], "....")
    cat("Tracing", call, msg, "\n")
}

.traceClassName <- function(className) {
    className[] <- paste0(className, "WithTrace")
    className
}

.assignOverBinding <- function(what, value, where, verbose = TRUE) {
    if(verbose) {
        pname <- getPackageName(where)
        msg <-
            gettextf("assigning over the binding of symbol %s in environment/package %s",
                     sQuote(what), sQuote(pname))
        message(strwrap(msg), domain = NA)
    }
    warnOpt <- options(warn= -1) # kill the obsolete warning from R_LockBinding
    on.exit(options(warnOpt))
    if(is.function(value)) {
        ## assign in the namespace for the function as well
        fenv <- environment(value)
        if(is.null(fenv)) # primitives
          fenv <- baseenv()
        if(!identical(fenv, where) && exists(what, envir = fenv, inherits = FALSE #?
                                             ) && bindingIsLocked(what, fenv)) {
            unlockBinding(what, fenv)
            assign(what, value, fenv)
            lockBinding(what, fenv)
        }
    }
    if(exists(what, envir = where, inherits = FALSE) && bindingIsLocked(what, where)) {
      unlockBinding(what, where)
      assign(what, value, where)
      lockBinding(what, where)
    }
    else
      assign(what, value, where)
}

.setMethodOverBinding <- function(what, signature, method, where, verbose = TRUE) {
    if(verbose)
        warning(gettextf("setting a method over the binding of symbol %s in environment/package %s",
                         sQuote(what),
                         sQuote(getPackageName(where))),
                domain = NA)
    if(exists(what, envir = where, inherits = FALSE)) {
        fdef <- get(what, envir = where)
        hasFunction <- is(fdef, "genericFunction")
    }

        hasFunction <- FALSE
    if(hasFunction) {
        ## find the generic in the corresponding namespace
        where2 <- findFunction(what, where = environment(fdef))[[1L]] # must find it?
        unlockBinding(what, where)
        setMethod(what, signature, method, where = where)
        lockBinding(what, where)
        ## assign in the package namespace as well
        unlockBinding(what, where2)
        setMethod(what, signature, method, where = where2)
        lockBinding(what, where2)
    }
    else {
        setMethod(what, signature, method, where = where)
    }
}

.getImportsEnv <- function(pkg) {
    iname = paste0("imports:", pkg)
    empty = emptyenv()
    env = asNamespace(pkg)

    while(!identical(env, empty)) {
        if (identical(attr(env, "name"), iname))
            return(env)
        env = parent.env(env)
    }
    NULL
}

.updateInImportsEnv <- function(what, newFun, importingPkg) {
    where = .getImportsEnv(importingPkg)
    if (!is.null(where) && (what %in% names(where))) {
        .assignOverBinding(what, newFun, where, FALSE)
    }
}

### finding the package name for a loaded namespace
.searchNamespaceNames <- function(env)
    paste0("namespace:", getNamespaceName(env))

.findFunEnvAndName <- function(what, where, signature = NULL) {
    pname <- character()
    if(is.null(signature)) {
        whereF <- findFunction(what, where = where)
        if(length(whereF)>0)
            whereF <- whereF[[1L]]
        else return(list(pname = pname, whereF = baseenv()))
    } else
        whereF <- .genEnv(what, where)

    ## avoid partial matches to "names"
    if("name" %in% names(attributes(whereF)))
        pname <- gsub("^.*:", "", attr(whereF, "name"))
    else if(isNamespace(whereF))
        pname <- .searchNamespaceNames(whereF)
    list(pname = pname, whereF = whereF)
}

.makeTraceClass <- function(traceClassName, className, verbose = TRUE) {
  ## called because the traceClassName not a class
  ## first check whether it may exist but not in the same package
  if(isClass(as.character(traceClassName)))
    return(as.character(traceClassName))
  if(verbose)
    message(sprintf("Constructing traceable class %s", dQuote(traceClassName)),
            domain = NA)
  env <- .classEnv(className)
  if(environmentIsLocked(env)) {
    message(gettextf("Environment of class %s is locked; using global environment for new class",
                     dQuote(className)),
            domain = NA)
    env <- .GlobalEnv
    packageSlot(traceClassName) <- NULL
  }
  setClass(traceClassName,
                 contains = c(className, "traceable"), where = env)
  if(existsMethod("show", className, env)) # override it for traceClassName
    setMethod("show", traceClassName, .showTraceable)
  traceClassName
}


## utils::globalVariables("fdef")
## .dummySetMethod <- function(f, signature = character(), definition,
## 	     where = topenv(parent.frame()), valueClass = NULL,
## 	     sealed = FALSE)
## {
##     if(is.function(f) && is(f, "genericFunction"))
##         f <- fdef@generic
##     else if(is.function(f)) {
##         if(is.primitive(f))
##             f <- .primname(f)
##         else
##             stop("a function for argument 'f' must be a generic function")
##     } else
##         f <- switch(f, "as.double" = "as.numeric", f)
##     assign(.dummyMethodName(f, signature), definition, envir = where)
## }

## .functionsOverriden <- c("setClass", "setClassUnion", "setGeneric", "setIs", "setMethod", "setValidity")

## .setEnvForSource <- function(env) {
##     doNothing <- function(x, ...)x
##     ## establish some dummy definitions & a special setMethod()
##     for(f in .functionsOverriden)
##         assign(f, switch(f, setMethod = .dummySetMethod, doNothing),
##                envir = env)
##     env
## }

.dummyMethodName <- function(f, signature)
    paste(c(f,signature), collapse="#")

.guessPackageName <- function(env) {
    allObjects <- names(env)
    ##allObjects <- allObjects[is.na(match(allObjects, .functionsOverriden))]
    ## counts of packages containing objects; objects not found don't count
    possible <- sort(table(unlist(lapply(allObjects, utils::find))),
                     decreasing = TRUE)
    if(length(possible) == 0)
        stop("none of the objects in the source code could be found:  need to attach or specify the package")
    else if(length(possible) > 1L) {
        global <- match(".GlobalEnv", names(possible), 0)
        if(global > 0) {
            possible <- possible[-global] # even if it's the most common
        }
        if(length(possible) > 1L)
            warning(gettextf("objects found in multiple packages: using %s and ignoring %s",
                             sQuote(names(possible[1L])),
                             paste(sQuote(names(possible[-1L])),
                                   collapse = ", ")),
                    domain = NA)
    }
    .rmpkg(names(possible[1L])) # the package name, or .GlobalEnv
}

## extract the new definitions from the source file
evalSource <- function(source, package = "", lock = TRUE, cache = FALSE) {
    if(!nzchar(package))
        envp <- .GlobalEnv # will look for the package after evaluating source
    else {
        pstring <- paste0("package:",package)
        packageIsVisible <- pstring %in% search()
        if(packageIsVisible) {
            envp <- as.environment(pstring)
##            envns <- tryCatch(asNamespace(package), error = function(cond) NULL)
        }
        else {
            envp <- tryCatch(asNamespace(package), error = function(cond) NULL)
##            envns <- envp
        }
        if(is.null(envp))
            stop(gettextf("package %s is not attached and no namespace found for it",
                          sQuote(package)),
                 domain = NA)
    }
    env <- new("sourceEnvironment", new.env(parent = envp),
        packageName = package,
        sourceFile = (if(is.character(source)) source else ""))
    env$.packageName <- package # Fixme: should be done by an initialize method
    setCacheOnAssign(env, cache)
    if(is(source, "character"))
        for(text in source) sys.source(text, envir = env)
    else if(is(source, "connection")) sys.source(source, envir = env)
    else if(!is(source, "environment"))
        stop(gettextf("invalid 'source' argument: expected file names or a connection but got an object of class %s",
                      dQuote(class(source)[[1L]])),
             domain = NA)
    if(lock)
        lockEnvironment(env, bindings = TRUE) # no further changes allowed
    env
}

insertSource <- function(source, package = "",
                         functions = allPlainObjects(),
                         methods = (if(missing(functions)) allMethodTables() else NULL)
##                         ,classes = (if(missing(functions)) allClassDefs() else NULL)
                         , force = missing(functions) & missing(methods)
                     ){
    MPattern <- .TableMetaPattern()
    CPattern <- .ClassMetaPattern()
    allPlainObjects <- function()
        allObjects[!(grepl(MPattern, allObjects) | grepl(CPattern, allObjects))]
    allMethodTables <- function()
        allObjects[grepl(MPattern, allObjects)]
##    allClassDefs <- function()
##        allObjects[grepl(CPattern, allObjects)]
    differs <- function(f1, f2)
        !(identical(body(f1), body(f2)) && identical(args(f1), args(f2)))
    if(is.environment(source) && !nzchar(package)) {
        if(is(source, "sourceEnvironment"))
            package <- source@packageName
        else if(exists(".packageName", envir = source, inherits = FALSE))
            package <- get(".packageName", envir =source)
    }
    if(is(source, "environment"))
        env <- source
    else
        env <- evalSource(source, package, FALSE) # sourceEnvironment, unlocked
    envPackage <- getPackageName(env, FALSE)
    ## identify an environment and (if possible) namespace for the package
    envp <- parent.env(env)
    if(identical(envp, .GlobalEnv) || !nzchar(envPackage)) { # no package name in the eval, guess one
        if(!nzchar(package))
            package <- .guessPackageName(env) # use find() on objects in env
        if(identical(package, ".GlobalEnv"))
            envns <- NULL
        else {
            pname <- paste0("package:", package)
            envp <- tryCatch(as.environment(pname), error = function(cond)NULL)
            if(is.null(envp)) {
                envp <- tryCatch(as.environment(pname), error = function(cond)NULL)
                if(is.null(envp))
                    stop(gettextf(
                     "cannot find an environment corresponding to package name \'%s\"",
                     package), domain = NA)
            }
            envns <- tryCatch(asNamespace(package), error = function(cond)NULL)
        }
        if(nzchar(package))
            assign(".packageName", package, envir = env)
    }
    else {
        if(isNamespace(envp))
            envns <- envp
        else
            envns <- tryCatch(asNamespace(package), error = function(cond)NULL)
    }
    if(nzchar(envPackage) && envPackage != package)
        warning(gettextf("supplied package, %s, differs from package inferred from source, %s",
                         sQuote(package), sQuote(envPackage)),
                domain = NA)
    packageSlot(env) <- package
    ## at this point, envp is the target environment (package or other)
    ## and envns is the corresponding namespace if any, or NULL
    allObjects <- names(env)
    allObjects <- allObjects[!(allObjects %in% c(".cacheOnAssign", ".packageName"))]
    ## Figure out what to trace.
    if(!missing(functions)) {
        notThere <- is.na(match(functions, allObjects))
        if(any(notThere)) {
            warning(gettextf("cannot insert these (not found in source): %s",
                    paste0('"', functions[notThere], '"',
                           collapse = ", ")),
                    domain = NA)
        }
    }
    .mnames <- allMethodTables()
    if(length(methods) > 0) {
	notThere <- vapply(methods, function(fname)
	    length(grep(fname, .mnames, fixed = TRUE)) == 0, NA)
        if(any(notThere)) {
            warning(gettextf("cannot insert methods for these functions (methods table not found in source): %s",
                    paste0('"', methods[notThere], '"',
                           collapse = ", ")),
                    domain = NA)
            methods <- methods[!notThere]
        }
        methodNames <- vapply(methods, function(fname)
                              .mnames[[grep(fname, .mnames, fixed = TRUE)[[1]]]], "")
    }
    else {
        methodNames <- .mnames
        methods <- sub(.TableMetaPrefix(), "", methodNames)
        methods <- sub(":.*","",methods)
    }
    ## if(!missing(classes)) {
    ##     .mnames <- allMethodNames()
    ##     notThere <- sapply(classes,
    ##      function(fname) length(grep(fname, .mnames, fixed = TRUE) == 0)
    ##     )
    ##     if(any(notThere)) {
    ##         warning(gettextf("Can't insert these classes (class definition not found in source): %s",
    ##                 paste('"',classes[notThere],'"',
    ##                       sep = "", collapse = ", ")),
    ##                 domain = NA)
    ##         classes <- classes[!notThere]
    ##     }
    ## }
    notTraceable <- newObjects <- objectsDone <- character()
    for(i in seq_along(functions)) {
        this <- functions[[i]]
        thisWhere <- NULL
        if(is.null(envns) ||
           exists(this, envir = envp, inherits = FALSE)) {
            envwhere <- envp
            thisWhere <- get(this, envir = envp)
        }
        else {
            envwhere <- envns
            if(is.environment(envns)  &&
               exists(this, envir = envns, inherits = FALSE))
                thisWhere <- get(this, envir = envns)
        }
        thisObj <- get(this, envir = env)
        if(is.function(thisObj) && is.function(thisWhere)
           && differs(thisObj, thisWhere)) {
            suppressMessages(
               .TraceWithMethods(this, where = envwhere, edit = env))
            objectsDone <- c(objectsDone, this)
        }
        else if(force)
            assign(this, thisObj, envir = envwhere)
        else if(!is.function(thisObj))
            notTraceable <- c(notTraceable, this)
        else if(is.null(thisWhere))
            newObjects <- c(newObjects, this)
    }
    if(length(notTraceable) > 0)
        message(gettextf("Non-function objects are not currently inserted (not traceable): %s",
                         paste(notTraceable, collapse = ", ")), domain = NA)
    if(length(newObjects) > 0)
        message(gettextf("New functions are not currently inserted (not untraceable): %s",
                         paste(newObjects, collapse = ", ")), domain = NA)
    if(length(objectsDone) > 0)
        message(gettextf("Modified functions inserted through trace(): %s",
                         paste(objectsDone, collapse = ", ")), domain = NA)
    for(i in seq_along(methods)) {
        .copyMethods(methods[[i]], methodNames[[i]], env, envp)
    }
    ## for(class in classes) {
    ##     .copyClass(class, env, envwhere)
    ## }
    ## return the environment, after cleaning up the dummy functions and
    ## adding a time stamp, if the source was parsed on this call
    if(!is.environment(source)) {
        lockEnvironment(env, bindings = TRUE) # no further changes allowed
        invisible(env)
    }
    else
        invisible(source)
}

.copyMethods <- function(f, tableName, env, envwhere) {
    differs <- function(o1, o2)
        !(is.function(o2) && # o2 can be NULL
          identical(body(o2), body(o2)) && identical(args(o1), args(o2)))
    table <- get(tableName, envir=env)
    fdef <- getGeneric(f, where = envwhere)
    if(!is(fdef, "genericFunction")) {
        message(gettextf("%s() is not a generic function in the target environment -- methods will not be inserted",
                         f), domain = NA)
        return(NULL)
    }
    curTable <- getMethodsForDispatch(fdef)
    allObjects <- sort(names(table))
    if(length(allObjects) > 0) {
        methodsInserted <- as.character(Filter(function(this) {
            def <- get(this, envir = table)
            curdef <- curTable[[this]]
            if(differs(def, curdef)) {
                suppressMessages(
                   .TraceWithMethods(f, signature = this, where = envwhere,
                              edit = env))
                TRUE
            } else
                FALSE
        }, allObjects))
        if(length(methodsInserted) > 0)
            message(gettextf("Methods inserted for function %s(): %s",
                  f, paste(methodsInserted, collapse =", ")),
                  domain = NA)
    }
}

.copyClass <- function(class, env, envwhere) {
    message("Pretend we inserted class ", class, domain = NA)
}

.findNewDefForTrace <- function(what, signature, env, package) {
    if(is.null(signature)) {
        if(exists(what, envir = env, inherits = FALSE))
            newObject <- get(what, envir = env)
        else
            stop(gettextf("no definition for object %s found in tracing environment",
                          sQuote(what)),
                 domain = NA)
    }
    else {
        ## we don't know the package for the generic (which may not
        ## be active), so we search for the string w/o package
        table <- .TableMetaName(what, "")
        allObjects <- sort(names(env))
        i <- grep(table, allObjects, fixed = TRUE)
        if(length(i) == 1)
            table <- env[[allObjects[[i]]]]
        else if(length(i) >1) {
            table <- allObjects[[i[[1]]]]
            warning(gettextf("multiple generics match pattern, using table %s", table)
                , domain = NA)
            table <- env[[table]]
        }
        else
            stop(gettextf("does not seem to be a method table for generic %s in tracing environment",
                          sQuote(what)),
                 domain = NA)
        if(exists(signature, envir = table, inherits = FALSE))
          newObject <- get(signature, envir = table)
        else
          stop(gettextf("no method in methods table for %s for signature %s",
                        sQuote(what),
                        sQuote(signature)),
               domain = NA)
    }
    newObject
}
