/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 1997--2025  The R Core Team
 *  Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
 *
 *  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/
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <Rinternals.h>
#include <math.h>
#include <limits.h> /* INT_MAX */
#include <stdlib.h> /* abs */
#include <Rmath.h> /* for imin2 and imax2 */
#include <R_ext/Print.h> /* for Rprintf */
#include <R_ext/Utils.h> /* for R_rsort */
#include <R_ext/Error.h>
#include <R_ext/Arith.h> /* for R_FINITE */

#ifdef ENABLE_NLS
#include <libintl.h>
#define _(String) dgettext ("graphics", String)
#else
#define _(String) (String)
#endif

static void stem_print(int close, int dist, int ndigits)
{
    if((close/10 == 0) && (dist < 0))
	Rprintf("  %*s | ", ndigits, "-0");
    else
	Rprintf("  %*d | ", ndigits, close/10);
}

static double rnd(double u, double c)
{
    return ((u < 0) ? (u*c - .5) : (u*c + .5));
}

static void // was Rboolean but return value is discarded
stem_leaf(double *x, int n, double scale, int width, double atom)
{
    double r, c, x1, x2;
    double mu, lo, hi;
    int mm, k, i, j, xi;
    int ldigits, hdigits, ndigits, pdigits;

    R_rsort(x,n);

#if 0
    if(n <= 1)
	return;
#endif

    Rprintf("\n");
    mu = 10;
    if(x[n-1] > x[0]) {
	r = atom + (x[n-1] - x[0])/scale;
	// this needs to be exact: exp10 in glibc is not accurate
	c = R_pow_di(10.0, (int)(1.0 - floor(log10(r))));
	mm = imin2(2, imax2(0, (int)(r*c/25)));
	k = 3*mm + 2 - 150/(n + 50);
	if ((k-1)*(k-2)*(k-5) == 0)
	    c *= 10.;
	/* need to ensure that x[i]*c does not integer overflow */
	x1 = fabs(x[0]); x2 = fabs(x[n-1]);
	if(x2 > x1) x1 = x2;
	while(x1*c > INT_MAX) c /= 10;
	if (k*(k-4)*(k-8) == 0) mu = 5;
	if ((k-1)*(k-5)*(k-6) == 0) mu = 20;
    } else {
	r = atom + fabs(x[0])/scale;
	c = R_pow_di(10.0, (int)(1.0 - floor(log10(r))));
    }

    /* Find the print width of the stem. */
    double
      xlow  = rnd(x[0], c),
      xhigh = rnd(x[n-1], c),
      lo_nd = floor(xlow/mu)*mu,
      hi_nd = floor(xhigh/mu)*mu;

    ldigits = (lo_nd < 0) ? (int) floor(log10(-lo_nd)) + 1 : 0;
    hdigits = (hi_nd > 0) ? (int) floor(log10(hi_nd)): 0;
    ndigits = (ldigits < hdigits) ? hdigits : ldigits;

    /* Starting cell */
    lo = floor(x[0]*c/mu)*mu;
    hi = floor(x[n-1]*c/mu)*mu;

    if(lo < 0 && floor(x[0]*c) == lo) lo = lo - mu;
    hi = lo + mu;
    if(floor(x[0]*c+0.5) > hi) {
	lo = hi;
	hi = lo + mu;
    }

    /* Print out the info about the decimal place */

    pdigits = 1 - (int) floor(log10(c) + 0.5);

    Rprintf("  The decimal point is ");
    if(pdigits == 0)
	Rprintf("at the |\n\n");
    else
	Rprintf("%d digit(s) to the %s of the |\n\n",abs(pdigits),
		(pdigits > 0) ? "right" : "left");
    i = 0;
    do {
	if(lo < 0)
	    stem_print((int)hi, (int)lo, ndigits);
	else
	    stem_print((int)lo, (int)hi, ndigits);
	j = 0;
	do {
	    xi = (int) rnd(x[i], c);

	    if( (hi == 0 && x[i] >= 0)||
		(lo <  0 && xi >  hi) ||
		(lo >= 0 && xi >= hi) )
		break;

	    j++;
	    if(j <= width-12)
		Rprintf("%1d", abs(xi) % 10);
	    i++;
	} while(i < n);
	if(j > width)
	    Rprintf("+%d", j - width);
	Rprintf("\n");
	if(i >= n)
	    break;
	hi += mu;
	lo += mu;
    } while(1);
    Rprintf("\n");
    return;
}

/* The R wrapper has removed NAs from x */
SEXP C_StemLeaf(SEXP x, SEXP scale, SEXP swidth, SEXP atom)
{
    if(TYPEOF(x) != REALSXP || TYPEOF(scale) != REALSXP) error("invalid input");
#ifdef LONG_VECTOR_SUPPORT
    if (IS_LONG_VEC(x))
	error(_("long vector '%s' is not supported"), "x");
#endif
    int width = asInteger(swidth), n = LENGTH(x);
    if (n == NA_INTEGER) error(_("invalid '%s' argument"), "x");
    if (width == NA_INTEGER) error(_("invalid '%s' argument"), "width");
    double sc = asReal(scale), sa = asReal(atom);
    if (!R_FINITE(sc)) error(_("invalid '%s' argument"), "scale");
    if (!R_FINITE(sa)) error(_("invalid '%s' argument"), "atom");
    stem_leaf(REAL(x), n, sc, width, sa);
    return R_NilValue;
}

/* Formerly a version in src/appl/binning.c */
#include <string.h> // for memset

static void 
C_bincount(double *x, R_xlen_t n, double *breaks, R_xlen_t nb, int *count,
	   int right, int include_border)
{
    R_xlen_t i, lo, hi, nb1 = nb - 1, new;

    // for(i = 0; i < nb1; i++) count[i] = 0;
    if (nb1)
	memset(count, 0, nb1 * sizeof(int));

    for(i = 0 ; i < n ; i++)
	if(R_FINITE(x[i])) { // left in as a precaution
	    lo = 0;
	    hi = nb1;
	    if(breaks[lo] <= x[i] &&
	       (x[i] < breaks[hi] || (x[i] == breaks[hi] && include_border))) {
		while(hi-lo >= 2) {
		    new = (hi+lo)/2;
		    if(x[i] > breaks[new] || (!right && x[i] == breaks[new]))
			lo = new;
		    else
			hi = new;
		}
#ifdef LONG_VECTOR_SUPPORT
		if(count[lo] >= INT_MAX)
		    error("count for a bin exceeds INT_MAX");
#endif
		count[lo]++;
	    }
	}
}

/* The R wrapper removed non-finite values */
SEXP C_BinCount(SEXP x, SEXP breaks, SEXP right, SEXP lowest)
{
    x = PROTECT(coerceVector(x, REALSXP));
    breaks = PROTECT(coerceVector(breaks, REALSXP));
    R_xlen_t n = XLENGTH(x), nB = XLENGTH(breaks);
    int sr = asLogical(right), sl = asLogical(lowest);
    if (sr == NA_INTEGER) error(_("invalid '%s' argument"), "right");
    if (sl == NA_INTEGER) error(_("invalid '%s' argument"), "include.lowest");
    SEXP counts = PROTECT(allocVector(INTSXP, nB - 1));
    C_bincount(REAL(x), n, REAL(breaks), nB, INTEGER(counts), sr, sl);
    UNPROTECT(3);
    return counts;
}
