#  File src/library/stats/R/runmed.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 2003-2025 The R Foundation
#  Copyright (C) 1995      Berwin A. Turlach
#  Ported to R, added interface to Stuetzle's code and further enhanced
#  by Martin Maechler,
#  Copyright (C) 1996-2002 Martin Maechler
#
#  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/


runmed <- function(x, k, endrule = c("median","keep","constant"),
                   algorithm = NULL,
                   na.action = c("+Big_alternate", "-Big_alternate", "na.omit", "fail"),
                   print.level = 0)
{
    n <- length(x)
    if(is.na(n)) stop(gettextf("invalid value of %s", "length(x)"), domain = NA)
    k <- as.integer(k)
    if(is.na(k)) stop(gettextf("invalid value of %s", "'k'"), domain = NA)
    if(k < 0L) stop("'k' must be positive")
    if(k %% 2L == 0L)
        warning(gettextf("'k' must be odd!  Changing 'k' to %d",
                         k <- as.integer(1+ 2*(k %/% 2))), domain = NA)
    if(n == 0L) {
	x <- double(); attr(x, "k") <- k
	return(x)
    }
    if (k > n)
        warning(gettextf("'k' is bigger than 'n'!  Changing 'k' to %d",
                         k <- as.integer(1+ 2*((n - 1)%/% 2))), domain = NA)
    algorithm <-
        if(missing(algorithm)) { ## use efficient default
            ## This is too primitive, MM knows better :
            if(k < 20L || n < 300L) "Stuetzle" else "Turlach"
        }
        else match.arg(algorithm, c("Stuetzle", "Turlach"))
    endrule <- match.arg(endrule)# including error.check
    iend <- switch(endrule,
                   ## "median" will be treated at the end
                   "median" =, "keep" = 0L,
                   "constant" = 1L)
    na.actions <- eval(formals()$na.action, NULL, baseenv())
    iNAct <- if(missing(na.action)) 1L else pmatch(na.action, na.actions)
    ## now,	na.action <- na.actions[[ iNAct ]]
    ## is equivalent to the original
    ##		na.action <- match.arg(na.action)
    ## which is here the same as  match.arg(na.action), choices=na.actions) :
    ## na.action <- match.arg(na.action, choices=na.actions)
    ## iNAct <- match(na.action, na.actions)
    if(print.level)
        cat(sprintf(paste0(
	    "runmed(x, k=%d, endrule='%s' ( => iend=%d), algorithm='%s',\n",
	    "       na.*='%s' ( => iNAct=%d))\n"),
		    k, endrule, iend, algorithm, na.actions[[iNAct]], iNAct))
    res <- switch(algorithm,
                  Turlach  = .Call(C_runmed, x, 1, k, iend, iNAct, print.level),
                  Stuetzle = .Call(C_runmed, x, 0, k, iend, iNAct, print.level))
    if(endrule == "median") res <- smoothEnds(res, k = k)

    ## Setting attribute has the advantage that the result immediately plots
    attr(res,"k") <- k
    res
}

### All the following is from MM:

smoothEnds <- function(y, k = 3)
{
    ## Purpose: Smooth end values---typically after runmed()
    ##-- (C) COPYRIGHT 1994,  Martin Maechler <maechler@stat.math.ethz.ch>

    ## med3(a,b,c) := median(a,b,c) - assuming no NA's in {a,b,c}
    med3 <- function(a,b,c)
    {
        m <- b
        if (a < b) {
            if (c < b) m <- if (a >= c) a  else  c
        } else {## a >= b
            if (c > b) m <- if (a <= c) a  else  c
        }
        m
    }
    med.3 <- function(x) { ## med.3(x) := median(x, na.rm=TRUE);  {length(x) == 3}
        if(anyNA(x))
            mean.default(x[!is.na(x)], na.rm=TRUE)
        else med3(x[[1L]], x[[2L]], x[[3L]])
    }
    med3i <- function(a,b,c) {
        if(anyNA(x <- c(a,b,c)))
            mean.default(x[!is.na(x)], na.rm=TRUE)
        else med3(a, b, c)
    }

    med.odd <- function(x, n = length(x))
    {
        ##  == median(x[1L:n]) IFF n is odd, slightly more efficient
        if(anyNA(x)) n <- length(x <- x[!is.na(x)])
        if(half <- (n + 1L) %/% 2L) # not empty
            sort(x, partial = half)[half]
        else x[1L] # NA, *not* empty
    }

    k <- as.integer(k)
    if (k < 0L || k %% 2L == 0L)
        stop("bandwidth 'k' must be >= 1 and odd!")
    k <- k %/% 2L
    if (k < 1L) return(y)
    ## else: k >= 1L: do something
    n <- length(y)
    n_1 <- n-1L; n_2 <- n-2L
    sm <- y
    if (k >= 2L) {
        sm [2L] <- med.3(y[1:3])
        sm[n_1] <- med.3(y[c(n,n_1,n_2)])

        ## Here, could use Stuetzle's strategy for MUCH BIGGER EFFICIENCY
        ##	(when k>=3 , k >> 1):
        ## Starting with the uttermost 3 points,
        ## always 'adding'  2 new ones, and determine the new median recursively
        ##
        if (k >= 3L) {
            for (i in 3:k) {
                j <- 2L*i - 1L
                sm  [i]    <- med.odd( y[1L:j]      , j) #- left border
                sm[n-i+1L] <- med.odd( y[(n+1L-j):n], j) #- right border
            }
        }
    }

    ##--- For the very first and last pt.:  Use Tukey's end-point rule: ---
    ## Ysm[1L]:= Median(Ysm[2L],X1,Z_0), where Z_0 is extrapol. from Ysm[2L],Ysm[3L]
    ## (to preserve integer sm[], use integer coefficients:
    sm[1L] <- med3i(y[1L], sm [2L], sm [2L] - 2L*(sm [3L] - sm[ 2L]))
    sm[n]  <- med3i(y[n],  sm[n_1], sm[n_1] - 2L*(sm[n_2] - sm[n_1]))
    sm
}
