#  File src/library/methods/R/MethodsListClass.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-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/

## 'FIXME' the function name is a *misnomer* since  MethodsList s are defunct
.InitMethodsListClass <- function(envir)
{
    if(exists(classMetaName("PossibleMethod"), envir))
        return(FALSE)
    clList <- character()

    ## the classes for method definitions
    setClass("PossibleMethod", where = envir); clList <- c(clList, "PossibleMethod")
    ## functions (esp. primitives) are methods
    setIs("function", "PossibleMethod", where = envir)

    ## the default slot of a generic function can be a method, primitive or NULL
    setClass("optionalMethod", where = envir); clList <- c(clList, "optionalMethod")
    setIs("PossibleMethod", "optionalMethod", where = envir)
    setIs("NULL", "optionalMethod", where = envir)
    ## prior to 2.11.0, the default slot in generic function objects was a MethodsList or NULL
    ## from 3.2.0, no longer:
    ## setIs("MethodsList", "optionalMethod", where = envir) #only until MethodsList class is defunct

    ## signatures -- multiple class names w. package slot in ||
    setClass("signature", representation("character", names = "character", package = "character"), where = envir); clList <- c(clList, "signature")

    ## className -- a single class name with package
    setClass("className", contains = "character",
             representation(package = "character"))

    ## formal method definition for all but primitives
    setClass("MethodDefinition", contains = "function",
             representation(target = "signature", defined = "signature", generic = "character"),
             where = envir); clList <- c(clList, "MethodDefinition")
    ## class for default methods made from ordinary functions
    setClass("derivedDefaultMethod", "MethodDefinition")
    ## class for methods that call and dispatch inside .Internal()
    setClass("internalDispatchMethod", contains = "derivedDefaultMethod",
             representation(internal = "character"))
    ## class for methods with precomputed information for callNextMethod
    setClass("MethodWithNext",
             representation("MethodDefinition", nextMethod = "PossibleMethod", excluded = "list"), where = envir); clList <- c(clList, "MethodWithNext")
    setClass("SealedMethodDefinition", contains = "MethodDefinition"); clList <- c(clList, "SealedMethodDefinition")
    setClass("genericFunction", contains = "function",
             representation( generic = "character", package = "character",
                            group = "list", valueClass = "character",
                            signature = "character", default = "optionalMethod",
                            skeleton = "call"), where = envir); clList <- c(clList, "genericFunction")
    ## standard generic function -- allows immediate dispatch
    setClass("standardGeneric",  contains = "genericFunction")
    setClass("nonstandardGeneric", # virtual class to mark special generic/group generic
             where = envir); clList <- c(clList, "nonstandardGeneric")
    setClass("nonstandardGenericFunction",
             representation("genericFunction", "nonstandardGeneric"),
             where = envir); clList <- c(clList, "nonstandardGenericFunction")
    setClass("groupGenericFunction",
             representation("genericFunction", groupMembers = "list"),
             where = envir); clList <- c(clList, "groupGenericFunction")
    setClass("nonstandardGroupGenericFunction",
             representation("groupGenericFunction", "nonstandardGeneric"),
             where = envir); clList <- c(clList, "nonstandardGroupGenericFunction")
    setClass("LinearMethodsList", representation(methods = "list", arguments = "list",
                                                 classes = "list", generic = "genericFunction"),
             where = envir); clList <- c(clList, "LinearMethodsList")
    setClass("ObjectsWithPackage", representation("character", package = "character"),
             where = envir); clList <- c(clList, "ObjectsWithPackage")
    assign(".SealedClasses", c(get(".SealedClasses", envir), clList), envir)
    TRUE
}


## Skeleton for the generic below
loadMethod <- function(method, fname, envir) method

## some initializations that need to be done late
.InitMethodDefinitions <- function(envir) {
    assign("asMethodDefinition",
           function(def, signature = list(.anyClassName), sealed = FALSE, fdef = def) {
        ## primitives can't take slots, but they are only legal as default methods
        ## and the code will just have to accommodate them in that role, w/o the
        ## MethodDefinition information.
        ## NULL is a valid def, used to remove methods.
        switch(typeof(def),
               "builtin" = , "special" = , "NULL" = return(def),
               "closure" = {},
               stop(gettextf("invalid object for formal method definition: type %s",
                             dQuote(typeof(def))),
                    domain = NA)
               )
        if(is(def, "MethodDefinition")) {
            value <- def
            if(missing(signature))
                signature <- value@defined
        }
        else
            value <- new("MethodDefinition", def)

        if(sealed)
            value <- new("SealedMethodDefinition", value)
        if(is(signature, "signature"))
            classes <- signature
        else
            classes <- .MakeSignature(new("signature"),  def, signature, fdef)
        value@target <- classes
        value@defined <- classes
        value
    }, envir = envir)
    setGeneric("loadMethod", where = envir)
    setMethod("loadMethod", "MethodDefinition",
              function(method, fname, envir) {
                  assign(".target",  method@target,  envir = envir)
                  assign(".defined", method@defined, envir = envir)
                  assign(".Generic", if(missing(fname)) method@generic else fname, envir = envir)
                  assign(".Method",  method, envir = envir)
                  method
              }, where = envir)
    setMethod("loadMethod", "MethodWithNext",
              function(method, fname, envir) {
                  callNextMethod()
                  assign(".nextMethod", method@nextMethod, envir = envir)
                  method
              }, where = envir)
    setGeneric("addNextMethod", function(method, f = "<unknown>",
                                         mlist, optional = FALSE, envir)
               standardGeneric("addNextMethod"), where = envir)
    setMethod("addNextMethod", "MethodDefinition",
	      function(method, f, mlist, optional, envir) {
		  .findNextFromTable(method, f, optional, envir)
	      }, where = envir)
    setMethod("addNextMethod", "MethodWithNext",
	      function(method, f, mlist, optional, envir) {
		  .findNextFromTable(method, f, optional, envir, method@excluded)
	      }, where = envir)

    .initGeneric <- function(.Object, ...) {
            value <- standardGeneric("initialize")
            if(!identical(class(value), class(.Object))) {
                cv <- class(value)
                co <- class(.Object)
                if(.identC(cv[[1L]], co)) {
                    ## ignore S3 with multiple classes or basic classes
                    ## Need to check length first.
                    if(length(cv) == 1L &&
                       is.na(match(cv, .BasicClasses))) {
                        warning(gettextf("missing package slot (%s) in object of class %s (package info added)",
                                         packageSlot(co),
                                         dQuote(class(.Object))),
                                domain = NA)
                        class(value) <- class(.Object)
                    }
                    else
                        return(value)
                }
                else
                    stop(gettextf("'initialize' method returned an object of class %s instead of the required class %s",
                                  paste(dQuote(class(value)), collapse=", "),
                                  dQuote(class(.Object))),
                         domain = NA)
            }
            value
        }
    if(!isGeneric("initialize", envir)) {
        ## save the default method
        assign(".initialize", initialize, envir)
        setGeneric("initialize",  .initGeneric, where = envir, useAsDefault = TRUE, simpleInheritanceOnly = TRUE)
    }
    setMethod("initialize", "signature",
              function(.Object, functionDef, ...) {
                  if(nargs() < 2)
                      .Object
                  else if(missing(functionDef))
                      .MakeSignature(.Object, , list(...))
                  else if(!is.function(functionDef))
                      .MakeSignature(.Object, , list(functionDef, ...))
                  else
                      .MakeSignature(.Object, functionDef, list(...))
              }, where = envir)
    setMethod("initialize", "environment", # only for new("environment",...); see .InitSpecialTypesAndClasses for subclasses
              function(.Object, ...) {
                  value <- new.env()
                  args <- list(...)
                  objs <- names(args)
                  for(what in objs)
                      value[[what]] <- args[[what]]
                  value
              }, where = envir)

    ## make sure body(m) <- .... leaves a method as a method
    setGeneric("body<-", where = envir)
    setMethod("body<-", "MethodDefinition", function (fun, envir, value) {
        ff <- as(fun, "function")
        body(ff, envir = envir) <- value
        fun@.Data <- ff
        fun
    }, where = envir)
    ## a show method for lists of generic functions, etc; see metaNameUndo
    if(!isGeneric("show", envir))
        setGeneric("show", where = envir, simpleInheritanceOnly = TRUE)
    setMethod("show", "ObjectsWithPackage",
              function(object) {
                  pkg <- object@package
                  data <- as(object, "character")
                  cat("An object of class \"", class(object), "\":\n", sep="")
                  if(length(unique(pkg))==1) {
                      show(data)
                      cat("(All from \"", unique(pkg), "\")\n", sep="")
                  }
                  else {
                      mat <- rbind(data, pkg)
		      dimnames(mat) <- list(c("Object:", "Package:"),
					    rep("", length(data)))
                      show(mat)
                  }
              }, where = envir)
    ## show method for reports of method selection ambiguities; see MethodsTable.R
    setMethod("show", "MethodSelectionReport", where = envir,
              function(object) {
                  nreport <- length(object@target)
                  cat(sprintf(ngettext(nreport,
                                       "Reported %d ambiguous selection out of %d for function %s\n",
                                       "Reported %d ambiguous selections out of %d for function %s\n"),
                              nreport, length(object@allSelections), object@generic))
                  target <- object@target; selected = object@selected
                  candidates <- object@candidates; note <- object@note
                  for(i in seq_len(nreport)) {
                      these <- candidates[[i]]; notei <- note[[i]]
                      these <- these[is.na(match(these, selected[[i]]))]
                      cat(gettextf(
                                   '%d: target "%s": chose "%s" (others: %s)',
                                   i,target[[i]], selected[[i]], paste0('"', these, '"', collapse =", ")))
                      if(nzchar(notei))
                          cat(gettextf("\n    Notes: %s.\n", notei))
                      else
                          cat(".\n")
                  }
                  NULL
              })
    setMethod("show", "classGeneratorFunction", where = envir,
              function(object) {
                  cat(gettextf("class generator function for class %s from package %s\n",
                               dQuote(object@className),
                               sQuote(object@package)))
                  show(as(object, "function"))
              })

    setGeneric("cbind2", function(x, y, ...) standardGeneric("cbind2"),
	       where = envir)
    ## and its default methods:
    setMethod("cbind2", signature(x = "ANY", y = "ANY"),
	      function(x,y, ...) .Internal(cbind(-1L, x, y)))
    setMethod("cbind2", signature(x = "ANY", y = "missing"),
	      function(x,y, ...) .Internal(cbind(-1L, x)))

    setGeneric("rbind2", function(x, y, ...) standardGeneric("rbind2"),
	       where = envir)
    ## and its default methods:
    setMethod("rbind2", signature(x = "ANY", y = "ANY"),
	      function(x,y, ...) .Internal(rbind(-1L, x, y)))
    setMethod("rbind2", signature(x = "ANY", y = "missing"),
	      function(x,y, ...) .Internal(rbind(-1L, x)))

    setGeneric("kronecker", where = envir)# <- unneeded?

    setMethod("kronecker", signature(X = "ANY", Y = "ANY"),
	      function(X, Y, FUN = "*", make.dimnames = FALSE, ...)
              .kronecker(X, Y, FUN = FUN, make.dimnames = make.dimnames, ...))

    .InitStructureMethods(envir)
    ## we want special initialize methods for basic classes:
    .InitBasicClassMethods(envir)
}

.InitStructureMethods <- function(where) {
    ## these methods need to be cached (for the sake of the primitive
    ## functions in the group) if a class is loaded that extends
    ## one of the classes in `needed` (other classes than "structure" now
    ## also require generics for some primitives).
    if(!exists(".NeedPrimitiveMethods", where))
      needed <- list()
    else
      needed <- get(".NeedPrimitiveMethods", where)
    needed <- c(needed, list(structure = "Ops", vector = "Ops",
          array = "Ops", nonStructure = "Ops"),
          array = "[", structure = "[", nonStructure = "[",
          structure = "Math", nonStructure = "Math",
          refClass = "$", refClass = "$<-", data.frame = "$<-"
                )
    assign(".NeedPrimitiveMethods", needed, where)
    setMethod("Ops", c("structure", "vector"), where = where,
              function(e1, e2) {
                  value <- callGeneric(if (isS4(e1)) e1@.Data else e1,
                                       if (isS4(e2)) e2@.Data else e2)
                  if(isS4(e1) && length(value) == length(e1)) {
                      e1@.Data <- value
                      e1
                  }
                  else
                    value
              })
    setMethod("Ops", c("vector", "structure"), where = where,
              function(e1, e2) {
                  value <- callGeneric(if (isS4(e1)) e1@.Data else e1,
                                       if (isS4(e2)) e2@.Data else e2)
                  if(isS4(e2) && length(value) == length(e2)) {
                      e2@.Data <- value
                      e2
                  }
                  else
                    value
              })
    setMethod("Ops", c("structure", "structure"), where = where,
              function(e1, e2)
                  callGeneric(if (isS4(e1)) e1@.Data else e1,
                              if (isS4(e2)) e2@.Data else e2)
              )
    ## We need some special cases for matrix and array.
    ## Although they extend "structure", their .Data "slot" is the matrix/array
    ## So op'ing them with a structure gives the matrix/array:  Not good?
    ## Following makes them obey the structure rule.
    setMethod("Ops", c("structure", "array"), where = where,
              function(e1, e2)
                 callGeneric(e1@.Data, as.vector(e2))
              )
    setMethod("Ops", c("array", "structure"), where = where,
              function(e1, e2)
                 callGeneric(as.vector(e1), e2@.Data)
              )
    ## but for two array-based strucures, we let the underlying
    ## code for matrix/array stand.
    setMethod("Ops", c("array", "array"), where = where,
              function(e1, e2)
                 callGeneric(e1@.Data, e2@.Data)
              )


    setMethod("Math", "structure", where = where,
              function(x) {
                  x@.Data <- callGeneric(x@.Data)
                  x
              })
    setMethod("Math2", "structure", where = where,
              function(x, digits) {
                  value <- x
                  x <- x@.Data
                  value@.Data  <- callGeneric()
                  value
              })
    ## some methods for nonStructure, ensuring that the class and slots
    ## will be discarded
    setMethod("Ops", c("nonStructure", "vector"), where = where,
              function(e1, e2) {
                  callGeneric(e1@.Data, e2)
              })
    setMethod("Ops", c("vector", "nonStructure"), where = where,
              function(e1, e2) {
                  callGeneric(e1, e2@.Data)
              })
    setMethod("Ops", c("nonStructure", "nonStructure"), where = where,
              function(e1, e2)
                 callGeneric(e1@.Data, e2@.Data)
              )
    setMethod("Math", "nonStructure", where = where,
              function(x) {
                  callGeneric(x@.Data)
              })
    setMethod("Math2", "nonStructure", where = where,
              function(x, digits) {
                  x <- x@.Data
                  callGeneric()
              })
    setMethod("[", "nonStructure", where = where,
                        function (x, i, j, ..., drop = TRUE)
                        {
                          value <- callNextMethod()
                          value@.Data
                        })

}


.MakeSignature <- function(object, def = NULL, signature, fdef = def) {
    ## fill in the signature information in object
    ## In effect, object must come from class "signature" or a subclass
    ## but the only explicit requirement is that it has compatible
    ## .Data and "package" slots
    signature <- unlist(signature)
    if(length(signature)>0) {
        classes <- as.character(signature)
        sigArgs <- names(signature)
        pkgs <- attr(signature, "package")
        if(is.null(pkgs))
            pkgs <- character(length(signature))
        if(is(fdef, "genericFunction"))
            formalNames <- fdef@signature
        else if(is.function(def)) {
            if(!is.function(fdef)) fdef <- def
            formalNames <- formalArgs(fdef)
            dots <- match("...", formalNames)
            if(!is.na(dots))
                formalNames <- formalNames[-dots]
        }
        else formalNames <- character()
        if(length(formalNames) > 0) {
            if(is.null(sigArgs))
              names(signature) <- formalNames[seq_along(classes)]
            else if(length(sigArgs) && anyNA(match(sigArgs, formalNames)))
                if(is(fdef, "genericFunction"))
                      stop(sprintf(gettext("the names in signature for method (%s) do not match %s's arguments (%s)", domain = "R-methods"),
                            paste(sigArgs, collapse = ", "),
                            fdef@generic,
                            paste(formalNames, collapse = ", ")),
                   domain = NA)
                else
                      stop(sprintf(gettext("the names in signature for method (%s) do not match function's arguments (%s)", domain = "R-methods"),
                            paste(sigArgs, collapse = ", "),
                            paste(formalNames, collapse = ", ")),
                   domain = NA)
        }
        object@.Data <- signature
        object@package <- pkgs
    }
    object
}
