/*  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 2010	The R Foundation
 *  Copyright (C) 2016	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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, a copy is available at
 *  https://www.R-project.org/Licenses/
 */

#include "modreg.h"
#include <math.h>

/* To be "exported" (as part of R's C API): */
/**
 * Modify the slopes  m_k := s'(x_k) using Fritsch & Carlson (1980)'s algorithm
 *
 * @param m  numeric vector of length n, the preliminary desired slopes s'(x_i), i = 1:n
 * @param S the divided differences (y_{i+1} - y_i) / (x_{i+1} - x_i);        i = 1:(n-1)
 * @param n  == length(m) == 1 + length(S)
 * @return m*: the modified m[]'s: Note that m[] is modified in place
 * @author Martin Maechler, Date: 19 Apr 2010
 */
void monoFC_mod(double *m, double S[], int n)
{
    if(n < 2)
	error(_("n must be at least two"));

    for(int k = 0; k < n - 1; k++) {
	/* modify both (m[k] & m[k+1]) if needed : */
	double Sk = S[k];
	int k1 = k + 1;
        if(Sk == 0.) { /* or |S| < eps ?? FIXME ?? */
	    m[k] = m[k1] = 0.;
        } else {
            double
		alpha = m[k ] / Sk,
		beta  = m[k1] / Sk, a2b3, ab23;
	    if((a2b3 = 2*alpha + beta - 3) > 0 &&
	       (ab23 = alpha + 2*beta - 3) > 0 &&
	       alpha * (a2b3 + ab23) < a2b3*a2b3) {
		/* we are outside the monotonicity region ==> fix slopes */
		double tauS = 3*Sk / sqrt(alpha*alpha + beta*beta);
		m[k ] = tauS * alpha;
		m[k1] = tauS * beta;
	    }
        }
    } /* end for */
}

SEXP monoFC_m(SEXP m, SEXP Sx)
{
    SEXP val;
    int n = LENGTH(m);

    if (isInteger(m))
	val = PROTECT(coerceVector(m, REALSXP));
    else {
	if (!isReal(m))
	    error(_("Argument m must be numeric"));
	val = PROTECT(duplicate(m));
    }
    if(n < 2) error(_("length(m) must be at least two"));
    if(!isReal(Sx) || LENGTH(Sx) != n-1)
	error(_("Argument Sx must be numeric vector one shorter than m[]"));

    /* Fix up the slopes m[] := val[]: */
    monoFC_mod(REAL(val), REAL(Sx), n);

    UNPROTECT(1);
    return(val);
}
