The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include "histogram.h"
#include "histo_perl_interf.h"

#include "mt.h"
#include "const-c.inc"

/* More HS_* macros to be found in histogram.h. Those here
 * are more chummy with perl than those in histogram.h, which only
 * currently use the memory allocation macros of perl. */

/* Ideally, HS_ASSERT_BIN_RANGE would be part of the histogram.h
 * API, but given that we know the unsigned data type here AND have
 * access to perl's croak conveniently, that seems premature cleanup. */
#define HS_ASSERT_BIN_RANGE(self, i) STMT_START {                                     \
  if (/* i < 0 || */ i >= self->nbins) {                                              \
    croak("Bin %u outside histogram range (highest bin index is %u", i, self->nbins); \
  } } STMT_END

#define HS_CLONE_GET_CLASS(classname, src, where) STMT_START {                        \
  if (!sv_isobject(src))                                                              \
    croak("Cannot call " #where "() on non-object");                                  \
  classname = sv_reftype(SvRV(src), TRUE);                                            \
  if ( !sv_isobject(src) || (SvTYPE(SvRV(src)) != SVt_PVMG) )                         \
    croak( "%s::" #where "() -- self is not a blessed SV reference", classname);      \
  } STMT_END


/* The following couple of lines are for the RNG, taken from Math::Random::MT */
typedef struct mt* Math__SimpleHisto__XS__RNG;

void*
U32ArrayPtr (pTHX_ int n)
{
  SV * sv = sv_2mortal( NEWSV( 0, n*sizeof(U32) ) );
  return SvPVX(sv);
}


MODULE = Math::SimpleHisto::XS    PACKAGE = Math::SimpleHisto::XS

REQUIRE: 2.2201

INCLUDE: const-xs.inc

INCLUDE: XS/rdgen.xs

INCLUDE: XS/construction.xs

INCLUDE: XS/bins.xs

INCLUDE: XS/aggregate.xs

void
DESTROY(self)
    simple_histo_1d* self
  CODE:
    HS_DEALLOCATE(self);

void
multiply_constant(self, factor = 1.)
    simple_histo_1d* self
    double factor
  CODE:
    if (factor < 0.)
      croak("Cannot multiply histogram with negative value %f", factor);
    histo_multiply_constant(self, factor);

void
add_histogram(self, operand)
    simple_histo_1d* self
    simple_histo_1d* operand
  ALIAS:
    subtract_histogram = 1
    multiply_histogram = 2
    divide_histogram = 3
  PREINIT:
    bool ok;
  CODE:
    if (ix == 0)
      ok = histo_add_histogram(self, operand);
    else if (ix == 1)
      ok = histo_subtract_histogram(self, operand);
    else if (ix == 2)
      ok = histo_multiply_histogram(self, operand);
    else
      ok = histo_divide_histogram(self, operand);

    if (!ok) {
      char *reason;
      if (ix == 0)
        reason = "add";
      else if (ix == 1)
        reason = "subtract";
      else if (ix == 2)
        reason = "multiply";
      else
        reason = "divide";
      croak("Failed to %s incompatible histogram. Binning not the same?", reason);
    }

void
normalize(self, normalization = 1.)
    simple_histo_1d* self
    double normalization
  CODE:
    if (normalization <= 0.)
      croak("Cannot normalize to %f", normalization);
    if (self->total == 0.)
      croak("Cannot normalize histogram without data");
    histo_multiply_constant(self, normalization / self->total);

void
fill(self, ...)
    simple_histo_1d* self
  CODE:
    if (items == 2) {
      SV* const x_tmp = ST(1);
      SvGETMAGIC(x_tmp);
      if (SvROK(x_tmp) && SvTYPE(SvRV(x_tmp)) == SVt_PVAV) {
        int i, n;
        SV** sv;
        double* x;
        AV* av = (AV*)SvRV(x_tmp);
        n = av_len(av);
        Newx(x, n+1, double);
        for (i = 0; i <= n; ++i) {
          sv = av_fetch(av, i, 0);
          if (UNLIKELY( sv == NULL )) {
            Safefree(x);
            croak("Shouldn't happen");
          }
          x[i] = SvNV(*sv);
        }
        histo_fill(self, n+1, x, NULL);
        Safefree(x);
      }
      else {
        double x = SvNV(ST(1));
        histo_fill(self, 1, &x, NULL);
      }
    }
    else if (items == 3) {
      SV* const x_tmp = ST(1);
      SV* const w_tmp = ST(2);
      SvGETMAGIC(x_tmp);
      SvGETMAGIC(w_tmp);
      if (SvROK(x_tmp) && SvTYPE(SvRV(x_tmp)) == SVt_PVAV) {
        int i, n;
        SV** sv;
        double *x, *w;
        AV *xav, *wav;
        if (UNLIKELY( !SvROK(w_tmp) || SvTYPE(SvRV(x_tmp)) != SVt_PVAV )) {
          croak("Need array of weights if using array of x values");
        }
        xav = (AV*)SvRV(x_tmp);
        wav = (AV*)SvRV(w_tmp);
        n = av_len(xav);
        if (UNLIKELY( av_len(wav) != n )) {
          croak("x and w array lengths differ");
        }

        Newx(x, n+1, double);
        Newx(w, n+1, double);
        for (i = 0; i <= n; ++i) {
          sv = av_fetch(xav, i, 0);
          if (UNLIKELY( sv == NULL )) {
            Safefree(x);
            Safefree(w);
            croak("Shouldn't happen");
          }
          x[i] = SvNV(*sv);

          sv = av_fetch(wav, i, 0);
          if (UNLIKELY( sv == NULL )) {
            Safefree(x);
            Safefree(w);
            croak("Shouldn't happen");
          }
          w[i] = SvNV(*sv);
        }
        histo_fill(self, n+1, x, w);
        Safefree(x);
        Safefree(w);
      }
      else {
        double x = SvNV(ST(1));
        double w = SvNV(ST(2));
        histo_fill(self, 1, &x, &w);
      }
    }
    else {
      croak("Invalid number of arguments to fill(self, ...)");
    }

void
fill_by_bin(self, ...)
    simple_histo_1d* self
  CODE:
    HS_INVALIDATE_CUMULATIVE(self);
    if (items == 2) {
      SV* const x_tmp = ST(1);
      SvGETMAGIC(x_tmp);
      if (SvROK(x_tmp) && SvTYPE(SvRV(x_tmp)) == SVt_PVAV) {
        int i, n;
        SV** sv;
        int* ibin;
        AV* av = (AV*)SvRV(x_tmp);
        n = av_len(av);
        Newx(ibin, n+1, int);
        for (i = 0; i <= n; ++i) {
          sv = av_fetch(av, i, 0);
          if (sv == NULL) {
            Safefree(ibin);
            croak("Shouldn't happen");
          }
          ibin[i] = SvIV(*sv);
        }
        histo_fill_by_bin(self, n+1, ibin, NULL);
        Safefree(ibin);
      }
      else {
        const int ibin = SvUV(ST(1));
        histo_fill_by_bin(self, 1, &ibin, NULL);
      }
    }
    else if (items == 3) {
      SV* const x_tmp = ST(1);
      SV* const w_tmp = ST(2);
      SvGETMAGIC(x_tmp);
      SvGETMAGIC(w_tmp);
      if (SvROK(x_tmp) && SvTYPE(SvRV(x_tmp)) == SVt_PVAV) {
        int i, n;
        SV** sv;
        int *ibin;
        double *w;
        AV *xav, *wav;
        if (!SvROK(w_tmp) || SvTYPE(SvRV(x_tmp)) != SVt_PVAV) {
          croak("Need array of weights if using array of bin numbers");
        }
        xav = (AV*)SvRV(x_tmp);
        wav = (AV*)SvRV(w_tmp);
        n = av_len(xav);
        if (av_len(wav) != n) {
          croak("ibin and w array lengths differ");
        }

        Newx(ibin, n+1, int);
        Newx(w, n+1, double);
        for (i = 0; i <= n; ++i) {
          sv = av_fetch(xav, i, 0);
          if (sv == NULL) {
            Safefree(ibin);
            Safefree(w);
            croak("Shouldn't happen");
          }
          ibin[i] = SvIV(*sv);

          sv = av_fetch(wav, i, 0);
          if (sv == NULL) {
            Safefree(ibin);
            Safefree(w);
            croak("Shouldn't happen");
          }
          w[i] = SvNV(*sv);
        }
        histo_fill_by_bin(self, n+1, ibin, w);
        Safefree(ibin);
        Safefree(w);
      }
      else {
        int ibin = SvIV(ST(1));
        double w = SvNV(ST(2));
        histo_fill_by_bin(self, 1, &ibin, &w);
      }
    }
    else {
      croak("Invalid number of arguments to fill(self, ...)");
    }


double
min(self)
    simple_histo_1d* self
  CODE:
    RETVAL = self->min;
  OUTPUT: RETVAL

double
max(self)
    simple_histo_1d* self
  CODE:
    RETVAL = self->max;
  OUTPUT: RETVAL

double
width(self)
    simple_histo_1d* self
  CODE:
    RETVAL = self->width;
  OUTPUT: RETVAL

double
overflow(self)
    simple_histo_1d* self
  CODE:
    RETVAL = self->overflow;
  OUTPUT: RETVAL

double
underflow(self)
    simple_histo_1d* self
  CODE:
    RETVAL = self->underflow;
  OUTPUT: RETVAL

double
total(self)
    simple_histo_1d* self
  CODE:
    RETVAL = self->total;
  OUTPUT: RETVAL

unsigned int
nbins(self)
    simple_histo_1d* self
  CODE:
    RETVAL = self->nbins;
  OUTPUT: RETVAL

unsigned int
highest_bin(self)
    simple_histo_1d* self
  CODE:
    /* I know. Trivial, but convienient! */
    RETVAL = self->nbins-1;
  OUTPUT: RETVAL

double
binsize(self, ibin = 0)
    simple_histo_1d* self
    unsigned int ibin
  CODE:
    HS_ASSERT_BIN_RANGE(self, ibin);
    if (self->bins == NULL)
      RETVAL = self->binsize;
    else
      RETVAL = self->bins[ibin+1] - self->bins[ibin];
  OUTPUT: RETVAL

unsigned int
nfills(self)
    simple_histo_1d* self
  CODE:
    RETVAL = self->nfills;
  OUTPUT: RETVAL


void
all_bin_contents(self)
    simple_histo_1d* self
  PREINIT:
    SV* rv;
  PPCODE:
    rv = histo_data_av(aTHX_ self);
    XPUSHs(sv_2mortal(rv));

void
set_all_bin_contents(self, new_data)
    simple_histo_1d* self
    AV* new_data
  PREINIT:
    unsigned int n, i;
    double* data;
    SV** elem;
  CODE:
    /* While this would be nicer in the histogram API, it will be much faster
     * to access the AV* on the fly instead of doing blanket conversion to remove
     * dependence on perl data structures, so this stays here for the time being. */
    HS_INVALIDATE_CUMULATIVE(self);
    n = self->nbins;
    if ((unsigned int)(av_len(new_data)+1) != n) {
      croak("Length of new data is %u, size of histogram is %u. That doesn't work.", (unsigned int)(av_len(new_data)+1), n);
    }
    data = self->data;
    for (i = 0; i < n; ++i) {
      elem = av_fetch(new_data, i, 0);
      if (elem == NULL) {
        croak("Shouldn't happen");
      }
      self->total -= data[i];
      data[i] = SvNV(*elem);
      self->total += data[i];
    }

double
bin_content(self, ibin)
    simple_histo_1d* self
    unsigned int ibin
  CODE:
    HS_ASSERT_BIN_RANGE(self, ibin);
    RETVAL = self->data[ibin];
  OUTPUT: RETVAL

void
set_bin_content(self, ibin, content)
    simple_histo_1d* self
    unsigned int ibin
    double content
  PPCODE:
    /* Would be nicer in the API, but again, this is faster. */
    HS_ASSERT_BIN_RANGE(self, ibin);
    HS_INVALIDATE_CUMULATIVE(self);
    self->total += content - self->data[ibin];
    self->data[ibin] = content;

void
set_underflow(self, content)
    simple_histo_1d* self
    double content
  PPCODE:
    /* This doesn't invalidate the INTERNAL cumulative histo */
    self->underflow = content;

void
set_overflow(self, content)
    simple_histo_1d* self
    double content
  PPCODE:
    /* This doesn't invalidate the INTERNAL cumulative histo */
    self->overflow = content;


void
set_nfills(self, nfills)
    simple_histo_1d* self
    unsigned int nfills
  PPCODE:
    /* This doesn't invalidate the INTERNAL cumulative histo */
    self->nfills = nfills;


#void
#binary_dump(self)
#    simple_histo_1d* self
#  PREINIT:
#    char* out;
#    SV* outSv;
#    double* tmp;
#    unsigned int size;
#  PPCODE:
#    size = sizeof(simple_histo_1d) + sizeof(double)*self->nbins;
#    outSv = newSVpvs("");
#    SvGROW(outSv, size+1);
#    printf("   %u\n", SvLEN(outSv));
#    out = SvPVX(outSv);
#    SvLEN_set(outSv, size);
#    printf("%u\n", SvLEN(outSv));
#    /*Newx(out, size+1, char);*/
#    tmp = self->data;
#    self->data = NULL;
#    Copy(self, out, sizeof(simple_histo_1d), char);
#    Copy(tmp, out+sizeof(simple_histo_1d), sizeof(double)*self->nbins, char);
#    out[size] = '\0';
#    printf("%u\n", SvLEN(outSv));
#    self->data = tmp;
#    XPUSHs(sv_2mortal(outSv));


void
rand(self, ...)
    simple_histo_1d* self
  PREINIT:
    double rndval;
    double retval;
    SV* rngsv;
    Math__SimpleHisto__XS__RNG rng;
    unsigned int ibin;
  PREINIT:
    simple_histo_1d* cum_hist;
  PPCODE:
    if (items > 1) {
      rngsv = ST(1);
    }
    else {
      rngsv = get_sv("Math::SimpleHisto::XS::RNG::Gen", 0);
      if (rngsv == 0) {
        croak("Cannot find default random number generator!");
      }
    }
    if (sv_derived_from(rngsv, "Math::SimpleHisto::XS::RNG")) {
      IV tmp = SvIV((SV*)SvRV(rngsv));
      rng = INT2PTR(Math__SimpleHisto__XS__RNG, tmp);
    }
    else
      Perl_croak(aTHX_ "%s: %s is not of type %s",
                  "Math::SimpleHisto::XS::rand",
                  "rng", "Math::SimpleHisto::XS::RNG");
    rndval = mt_genrand(rng);

    /* Get the properly normalized internal cumulative */
    HS_ASSERT_CUMULATIVE(self);
    cum_hist = self->cumulative_hist;

    /* This all operates on the cumulative histogram */
    ibin = rndval < cum_hist->data[0] ? 0 : find_bin_nonconstant(rndval, cum_hist->nbins, cum_hist->data);
    if (cum_hist->bins == 0) { /* constant bin size */
      retval = cum_hist->min + cum_hist->binsize * (double)(ibin+1);
      if (rndval > cum_hist->data[ibin]) {
        retval += cum_hist->binsize * (rndval - cum_hist->data[ibin])
                                      / (cum_hist->data[ibin+1] - cum_hist->data[ibin]);
      }
    }
    else { /* variable bin size */
      retval = cum_hist->bins[ibin+1];
      if (rndval > cum_hist->data[ibin]) {
        retval += (cum_hist->bins[ibin+1] - cum_hist->bins[ibin])
                  * (rndval - cum_hist->data[ibin])
                  / (cum_hist->data[ibin+1] - cum_hist->data[ibin]);
      }
    }
    XPUSHs(sv_2mortal(newSVnv(retval)));


void
_get_info(self)
    simple_histo_1d* self
  PREINIT:
    SV* data_ary;
    SV* bins_ary;
  PPCODE:
    /* min, max, nbins, nfills, overflow, underflow, dataref, binsref*/
    EXTEND(SP, 8);
    mPUSHn(self->min);
    mPUSHn(self->max);
    mPUSHu(self->nbins);
    mPUSHu(self->nfills);
    mPUSHn(self->overflow);
    mPUSHn(self->underflow);
    data_ary = histo_data_av(aTHX_ self);
    XPUSHs(sv_2mortal(data_ary));
    if (self->bins == NULL)
      bins_ary = &PL_sv_undef;
    else
      bins_ary = sv_2mortal(histo_bins_av(aTHX_ self));
    XPUSHs(bins_ary);