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

#include "ppport.h"

#include "src/sort_bucket.c"

MODULE = Sort::Bucket		PACKAGE = Sort::Bucket		

void
inplace_bucket_sort(av, bucket_bits=0)
        AV * av
        int bucket_bits
PROTOTYPE: \@;$
INIT:
        int len;
CODE:
    len = 1 + av_len(av);

    if (SvREADONLY(av))
        croak("%s", PL_no_modify);

    if (bucket_bits) {
        if (bucket_bits < 1 || bucket_bits > 31) {
            croak("%s", "bucket bits out of range");
        }
    } else if (len < 1024) {
        bucket_bits = 8;
    } else {
        int i = len>>12;
        bucket_bits = 8;
        while (i) {
            ++bucket_bits;
            i >>= 1;
        }
    }

    SvREADONLY_on(av);
    do_bucket_sort(AvARRAY(av), len, bucket_bits);
    SvREADONLY_off(av);

=head1 FUNCTIONS FOR TESTING

These functions are intended for use in the test suite.

=over

=item _set_readonly_for_testing(B<av>)

Sets the readonly flag on an AV.

=cut

void
_set_readonly_for_testing(av)
    AV * av
PROTOTYPE: \@
CODE:
    SvREADONLY_on(av);    

=item _ms_do_mergesort_testharness(B<values>, B<offset>, B<sortlen>)

This function gives the test suite low-level access to ms_do_mergesort().

The B<values> AV must hold U32 values.  Calls ms_do_mergesort() on
B<values>[B<offset> .. B<offset>+B<sortlen>-1], which will sort those
elements partially in place, moving them down B<sortlen>-1 slots in the
process.  See F<src/mergesort_algo.c>.

After this function returns, the elements of B<values> will be error
message strings if a problem was detected, otherwise they will be integers
giving the index within B<values> of the element that was moved to this
slot by the sort.

=cut

void
_ms_do_mergesort_testharness(values, offset, sortlen)
    AV* values
    int offset
    int sortlen
PROTOTYPE: \@$$
INIT:
    int vec_len, i, j;
    ms_elt_t *storage, *orig_storage;
    unsigned char *pv;
    STRLEN cur;
    SV *dummy_sv_vector;
CODE:
    if (sortlen < 1) {
        croak("sortlen < 1");
    }
    vec_len = 1 + av_len(values);
    if (offset + sortlen > vec_len) {
        croak("sort would read off the top of values");
    }
    if (offset < sortlen-1) {
        croak("sort would write off the bottom of values");
    }

    Newx(storage, vec_len, ms_elt_t);
    Newxz(dummy_sv_vector, vec_len, SV);
    for ( i=0 ; i<vec_len ; i++ ) {
        SV** elt = av_fetch(values, i, 0);
        if (!elt) {
            Safefree(dummy_sv_vector); Safefree(storage);
            croak("missing elt at %d", i);
        }
        storage[i].mse_key = SvUV(*elt);
        storage[i].mse_sv  = dummy_sv_vector + i;
    }
    
    Newx(orig_storage, vec_len, ms_elt_t);
    Copy(storage, orig_storage, vec_len, ms_elt_t);
    ms_do_mergesort(storage+offset, sortlen);

    for ( i=0 ; i<vec_len ; i++ ) {
        SV** elt = av_fetch(values, i, 0);
        int orig_pos = storage[i].mse_sv - dummy_sv_vector;
        if (orig_pos < 0 || orig_pos >= vec_len) {
            sv_setpv(*elt, "mse_sv out of bounds");
        } else if ( storage[i].mse_key != orig_storage[orig_pos].mse_key ) {
            sv_setpv(*elt, "mse_key does not match mse_sv");
        } else {
            sv_setiv(*elt, orig_pos);
        }
    }

    Safefree(orig_storage);
    Safefree(storage);
    Safefree(dummy_sv_vector);

=item _cbv_testharness(B<sv>, B<bits>, B<major_out>, B<minor_out>)

Compute the major and minor bucket values of a single SV.

=cut

void
_cbv_testharness(sv, bits, major_out, minor_out)
    SV* sv
    int bits
    SV* major_out
    SV* minor_out
PROTOTYPE: $$$$
INIT:
    U32 b_major[1], b_minor[1];
CODE:
    if (!sv || !major_out || !minor_out)
        croak("null SV*");
    if (bits < 1)
        croak("bits < 1");
    if (bits > 31) 
        croak("bits > 31");

    calculate_bucket_values(1, &sv, b_major, b_minor, bits);

    sv_setnv(major_out, (double)b_major[0]);
    sv_setnv(minor_out, (double)b_minor[0]);

=item _cbv_testharness_check_for_descending(B<array>, B<bits>)

Compute the major and minor bucket values for an array, using B<bits>
major bits.  Scan through the bucket values and identify the index of the
first element with bucket values lower than the previous element.
Return the index, or 0 if the bucket value sequence is non-descending.

Handy for testing - you feed it a sorted array and if it finds a
point where the bucket values descend that's a bug in the bucket value
calculation.

=cut

int
_cbv_testharness_check_for_descending(array, bits)
    AV* array
    int bits
PROTOTYPE: \@$
INIT:
    U32 *b_major, *b_minor;
    int len, i;
CODE:
    if (bits < 1)
        croak("bits < 1");
    if (bits > 31) 
        croak("bits > 31");

    len = 1 + av_len(array);
    Newx(b_major, len, U32);
    Newx(b_minor, len, U32);
    calculate_bucket_values(len, AvARRAY(array), b_major, b_minor, bits);

    RETVAL = 0;
    for ( i=1 ; i<len ; i++ ) {
        if (b_major[i] < b_major[i-1] 
          || (b_major[i] == b_major[i-1] && b_minor[i] < b_minor[i-1])
           ) {
            /* disorder at i */
            RETVAL = i;
            break;
        }
    }

    Safefree(b_major);
    Safefree(b_minor);
OUTPUT:
    RETVAL

=back

=cut