/*
=begin
= Class MultiBitNums

Class of multiple multi-bit data packed in a C string.
The number of bits is fixed to an arbitrary value.
For example, it can accommodate 11-bit integers (but cannot
a mixture of integers with different lengths).

=== Function overview

Integer encoding/decoding (so far only decoding has been implemented):

  * Each segment of data is assumed to be multi-bit (fixed-length)
    unsigned integer in network byte order (big endian).

  * Note: integers of the system does not have to be big-endian (Only 
    the binary data is interpreted as unsigned big-endian).


===Class Methods
---MultiBitNums.new( str, nbit  [, nint] )
     Creates a new object from a binary data (string) containing multi-bit 
     segments.

     ARGUMENTS
     * str (String): binary data containing multi-bit data
     * nbit (Integer): length in bits of each segments contained in str
     * nint (Integer, can be omitted): number of nbit-bit data in str. If
       omitted, derived automatically from the length of str.

     RETURN VALUE
     * a MultiBitNums object

     ERRORS
     * exception is raised if nint is too large for the length of str.

===Instance Methods
---to_int32str
     Converts into a string containing binary data of 32-bit integers
     of the system. Useful with NArray.

     ARGUMENTS
       (none)

     RETURN VALUE
     * a String (Its binary expression depends on the integer
       expression of the system). 
     
     ERRORS
     * exception is raised if nbit (see ((<MultiBitNums.new>))) is greater than 32.

     EXAMPLE
       mb = MultiBitNums.new(str, nbits)
       require "narray"           # download it from RAA at www.ruby-lang.org
       ary = NArray.to_na( mb, "int" )  # the data is read into a NArray ary

=end
 */

/* ruby.h for rb_raise */
#include "ruby.h"

/* sys/typed.h : should be configured whether the system has this or not */
#include <sys/types.h>

#ifndef HAVE_INT32_T
typedef long int32_t;
#endif

#ifndef RSTRING_PTR
#define RSTRNG_PTR(s) (RSTRING(s)->ptr)
#endif
#ifndef RSTRING_LEN
#define RSTRING_LEN(a) (RSTRING(a)->len)
#endif

static VALUE mNumRu;
static VALUE cMultiBitNums;

struct MultiBitNums {
     unsigned char *ptr;  /* holds data */
     long len_ptr;        /* length of ptr (in bytes) */
     int  nbit;           /* length in bits of each integer in str */
     long nint;           /* number of data contained (<= len_str*8/static) */
};

static void
mltbtnm_free(struct MultiBitNums *mbs)
{
    xfree(mbs->ptr); 
    mbs->len_ptr = 0;
    mbs->nbit = 0;
    mbs->nint = 0;
    xfree(mbs);
}

static VALUE
mltbtnm_s_new(int argc, VALUE *argv, VALUE klass)
{
    /* [ actual arguments --> */
    VALUE str;   
    VALUE nbit;
    VALUE nint;         /* can be omitted */
    /* <-- actual arguments ] */

    struct MultiBitNums *mbs;
    long c_nint, nintmax;

    if (argc < 2 || argc >3){
	rb_raise(rb_eArgError, 
	  "Usage: MultiBitNums.new(str, nbit [,nint]) -- nint is omittable");
    }
    str = argv[0];
    nbit = argv[1];

    mbs = ALLOC(struct MultiBitNums);
    mbs->nbit = NUM2INT(nbit);
    mbs->len_ptr = RSTRING_LEN(str);
    mbs->ptr = ALLOC_N(char, mbs->len_ptr);
    memcpy( mbs->ptr, RSTRING_PTR(str), mbs->len_ptr);

    nintmax = ((mbs->len_ptr*8.0)/mbs->nbit + 0.1);
    if ( argc != 3 ){
	mbs->nint = nintmax;
    } else {
	nint = argv[2];
	c_nint = NUM2LONG(nint);
	if (c_nint > nintmax) {
	    rb_raise(rb_eArgError, "nint is too long");
	} else {
	    mbs->nint = c_nint;
	}
    }
    return Data_Wrap_Struct(klass, 0, mltbtnm_free, mbs);
}

static int32_t *
mltbtnm_read_int32(unsigned char *ptr, long len_ptr, int nbit, long *nint)
     /*
     unsigned char *ptr  // holds data
     long len_ptr        // length of ptr (in bytes)
     int  nbit           // number of bits
     long *nint          // number of integers to read (if <= 0, determined 
                         // from len_ptr)
     */
{
    int32_t *result;
    long nintmax, i, n0, n1, k0, k1, w;
    long j, j2, nm;
    unsigned char cn[4],mk0,mb;

    nintmax = ((len_ptr*8.0)/nbit + 0.1);
          /* double was used here to prevent overflow. 0.1 is for round errs */
    if ( *nint <= 0 ){
	*nint = nintmax;
    } else if (*nint > nintmax) {
	rb_raise(rb_eArgError, "*nint is too long");
    }
    if ( nbit > 32 ){
	rb_raise(rb_eArgError, "nbit must be 32 or less");
    }

    result = (int32_t *) malloc((*nint)*sizeof(int32_t));

    for(i=0; i<(*nint); i++){
	w = (i % 8) * nbit;
	n0 = (i / 8) * nbit + (w / 8);     /* overflow-free i*nbit/8 */
	k0 = w % 8;                        /* == (i*nbit) % 8 */
	w = k0 + nbit - 1;
	n1 = n0 + (w/8);
	k1 = w % 8;
	mk0 = 0xff >> k0;   /* to mask 0..k0-1 bits */
	for(j=n1; j>= n1-3; j--){
	    j2 = j - n1 + 3;  /* 3,2,1,0 */
	    mb = (nm = (8-nbit+(3-j2)*8))  > 0 ? (0xff>>nm): 0xff;
	    if (j > n0) {
		cn[j2] = ptr[j] >> (7-k1);
		if (j != n0+1){
		    cn[j2] += ptr[j-1] << (k1+1);
		} else {
		    cn[j2] += (ptr[j-1] & mk0) << (k1+1); 
		}
		cn[j2] &= mb;
	    } else if (j == n0) {
		cn[j2] = (ptr[j] & mk0) >> (7-k1);
		cn[j2] &= mb;
	    } else {
		/* j < n0 --> blank */
		cn[j2] = 0x00;
	    }
	}
	result[i] = 0x1000000*cn[0] + 0x10000*cn[1] + 0x100*cn[2] + cn[3];
    }
    return(result);
}

static VALUE
mltbtnm_to_int32str(VALUE obj){
    struct MultiBitNums *mbs;
    int32_t *buff;

    Data_Get_Struct(obj, struct MultiBitNums, mbs);
    buff = mltbtnm_read_int32(mbs->ptr, mbs->len_ptr, mbs->nbit, &(mbs->nint));
    return rb_str_new( (char *) buff, mbs->nint * sizeof(int32_t) );
}


void
Init_multibitnums()
{
    extern VALUE ruby_class;
    mNumRu = rb_define_module("NumRu");
    cMultiBitNums = rb_define_class_under(mNumRu, "MultiBitNums", rb_cObject);
    rb_define_singleton_method(cMultiBitNums,"new",mltbtnm_s_new,-1);
    rb_define_method(cMultiBitNums, "to_int32str", mltbtnm_to_int32str,0);
}
