#  File src/library/stats/R/spline.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-2019 The R Core Team
#                2002 Simon N. Wood
#
#  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/

#### 'spline' and 'splinefun' are very similar --- keep in sync!
####               --------- has more
####  also consider ``compatibility'' with  'approx' and 'approxfun'

spline <-
    function(x, y = NULL, n = 3*length(x), method = "fmm",
             xmin = min(x), xmax = max(x), xout, ties = mean)
{
    method <- pmatch(method, c("periodic", "natural", "fmm", "hyman"))
    if(is.na(method)) stop("invalid interpolation method")

    x <- regularize.values(x, y, ties, missing(ties)) # -> (x,y) numeric of same length
    y <- x$y
    x <- x$x
    nx <- length(x) # large vectors ==> non-integer
    if(is.na(nx)) stop(gettextf("invalid value of %s", "length(x)"), domain = NA)
    if(nx == 0) stop("zero non-NA points")

    if(method == 1L && y[1L] != y[nx]) { # periodic
        warning("spline: first and last y values differ - using y[1] for both")
        y[nx] <- y[1L]
    }
    if(method == 4L) {
        dy <- diff(y)
        if(!(all(dy >= 0) || all(dy <= 0)))
            stop("'y' must be increasing or decreasing")
    }

    if(missing(xout)) xout <- seq.int(xmin, xmax, length.out = n)
    else n <- length(xout)
    if (n <= 0L) stop("'spline' requires n >= 1")
    xout <- as.double(xout)

    z <- .Call(C_SplineCoef, min(3L, method), x, y)
    if(method == 4L) z <- spl_coef_conv(hyman_filter(z))
    list(x = xout, y = .Call(C_SplineEval, xout, z))
}

### Filters cubic spline function to yield co-monotonicity in accordance
### with Hyman (1983) SIAM J. Sci. Stat. Comput. 4(4):645-654, z$x is knot
### position z$y is value at knot z$b is gradient at knot. See also
### Dougherty, Edelman and Hyman 1989 Mathematics of Computation 52:471-494.
### Contributed by Simon N. Wood, improved by R-core.
### https://stat.ethz.ch/pipermail/r-help/2002-September/024890.html
hyman_filter <- function(z)
{
    n <- length(z$x)
    ss <- diff(z$y) / diff(z$x)
    S0 <- c(ss[1L], ss)
    S1 <- c(ss, ss[n-1L])
    t1 <- pmin(abs(S0), abs(S1))
    sig <- z$b
    ind <- S0*S1 > 0
    sig[ind] <- S1[ind]
    ind <- sig >= 0
    if(sum(ind)) z$b[ind] <- pmin(pmax(0, z$b[ind]), 3*t1[ind])
    ind <- !ind
    if(sum(ind)) z$b[ind] <- pmax(pmin(0, z$b[ind]), -3*t1[ind])
    z
}


### Takes an object z containing equal-length vectors
### z$x, z$y, z$b, z$c, z$d defining a cubic spline interpolating
### z$x, z$y and forces z$c and z$d to be consistent with z$y and
### z$b (gradient of spline). This is intended for use in conjunction
### with Hyman's monotonicity filter.
### Note that R's spline routine has s''(x)/2 as c and s'''(x)/6 as d.
### Contributed by Simon N. Wood, improved by R-core.
spl_coef_conv <- function(z)
{
    n <- length(z$x)
    h <- diff(z$x); y <- -diff(z$y)
    b0 <- z$b[-n]; b1 <- z$b[-1L]
    cc <- -(3*y + (2*b0 + b1)*h) / h^2
    c1 <- (3*y[n-1L] + (b0[n-1L] + 2*b1[n-1L])*h[n-1L]) / h[n-1L]^2
    z$c <- c(cc, c1)
    dd <- (2*y/h + b0 + b1) / h^2
    z$d <- c(dd, dd[n-1L])
    z
}
