#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
/* We're not using anything for which we need ppport.h */
#include "bitlist.h"
#include "sequences.h"

#define CHECKPOS \
  if (list->pos >= list->len) \
      XSRETURN_UNDEF;

#ifdef INT_MAX
  #define MAX_COUNT INT_MAX
#else
  #define MAX_COUNT 1000000000 /* big enough */
#endif

/*
 * Instead of using XPUSH to extend the stack one value at a time, we'll
 * extend this many when we run out.
 */
#define BLSTGROW 64

static int is_positive_number(const char* str) {
  size_t i;
  size_t len = strlen(str);
  if (len == 0)
    return 0;
  for (i = 0; i < len; i++) {
    if (!isdigit(str[i]))
      return 0;
  }
  return 1;
}
static UV get_uv_from_st(SV* sv) {
  if ( !SvOK(sv) )
    croak("value must be >= 0");  /* undef */
  if ( (SvIV(sv) < 0) && !is_positive_number(SvPV_nolen(sv)) )
    croak("value must be >= 0");  /* negative number */
  return SvUV(sv);
}


static int parse_binary_string(const char* str, UV* val) {
  UV v = 0;
  size_t i;
  size_t len = strlen(str);
  if (len == 0)
    return 0;
  for (i = 0; i < len; i++) {
    if      (str[i] == '0') { v = 2*v + 0; }
    else if (str[i] == '1') { v = 2*v + 1; }
    else                    { return 0; }
  }
  if (val != 0)
    *val = v;
  return len;
}


/* This is C99, and has to be wrapped in HAS_C99_VARIADIC_MACROS.
 * TODO: Find a non-variadic way to do the same thing.
 */
#define GET_CODEVP(codename, nargs, ...) \
   { \
    bool wantarray = (GIMME_V == G_ARRAY); \
    WTYPE v; \
    int c = 0; \
    if ( (list == 0) || (count == 0) || (list->pos >= list->len) ) { \
      if (wantarray) { XSRETURN_EMPTY; } else { XSRETURN_UNDEF; } \
    } \
    if (list->is_writing) { \
      croak("read while writing with %s", #codename); \
      if (wantarray) { XSRETURN_EMPTY; } else { XSRETURN_UNDEF; } \
    } \
    if (count < 0)  count = MAX_COUNT; \
    if (!wantarray) { \
      v = 0; \
      while ( (c++ < count) && (list->pos < list->len) ) \
        v = get_ ## codename(__VA_ARGS__); \
      PUSHs(sv_2mortal(newSVuv(  v  ))); \
    } else { \
      int st_size = 0; \
      int st_pos  = 0; \
      if (count < 10000) { \
        EXTEND(SP, count); \
        st_size = count; \
      } \
      while ( (c++ < count) && (list->pos < list->len) ) { \
        if (++st_pos > st_size) { EXTEND(SP, BLSTGROW); st_size += BLSTGROW; } \
        v = get_ ## codename(__VA_ARGS__); \
        PUSHs(sv_2mortal(newSVuv(  v  ))); \
      } \
    } \
   }

#define PUT_CODEVP(codename, nargs, ...) \
    if (!list->is_writing) { \
      croak("write while reading"); \
    } else { \
      int c = (nargs); \
      while (++c < items) { \
        UV n = get_uv_from_st(ST(c)); \
        put_ ## codename(__VA_ARGS__, n); \
      } \
    }

#define GET_CODE(cn)          GET_CODEVP(cn, 0, list)
#define PUT_CODE(cn)          PUT_CODEVP(cn, 0, list)
#define GET_CODEP(cn,a)       GET_CODEVP(cn, 1, list, a)
#define PUT_CODEP(cn,a)       PUT_CODEVP(cn, 1, list, a)
#define GET_CODEPP(cn,a,b)    GET_CODEVP(cn, 2, list, a, b)
#define PUT_CODEPP(cn,a,b)    PUT_CODEVP(cn, 2, list, a, b)
#define GET_CODESPP(cn,s,a,b) GET_CODEVP(cn, 2, list, s, a, b)
#define PUT_CODESPP(cn,s,a,b) PUT_CODEVP(cn, 2, list, s, a, b)

typedef BitList* Data__BitStream__XS;

MODULE = Data::BitStream::XS	PACKAGE = Data::BitStream::XS

PROTOTYPES: ENABLE

Data::BitStream::XS
new (IN const char* package, ...)
  PREINIT:
    int i;
    FileMode mode = eModeRW;
    const char* file = 0;
    const char* fheaderdata = 0;
    int   fheaderlines = 0;
    int   initial_bits = 0;
  CODE:
    if (items > 1) {
      if ( (items % 2) == 0)
        croak("new takes a hash of options");
      for (i = 1; i < items; i += 2) {
        STRLEN klen, vlen;
        const char* key = SvPV(ST(i+0), klen);
        if (!strcmp(key, "mode")) {
          const char* val = SvPV(ST(i+1), vlen);
          if     ((!strcmp(val,"r" ))||(!strcmp(val,"read")))      mode=eModeR;
          else if((!strcmp(val,"ro"))||(!strcmp(val,"readonly")))  mode=eModeRO;
          else if((!strcmp(val,"w" ))||(!strcmp(val,"write")))     mode=eModeW;
          else if((!strcmp(val,"wo"))||(!strcmp(val,"writeonly"))) mode=eModeWO;
          else if((!strcmp(val,"a" ))||(!strcmp(val,"append")))    mode=eModeA;
          else if((!strcmp(val,"rw"))||(!strcmp(val,"rdwr"))
                                     ||(!strcmp(val,"readwrite"))) mode=eModeRW;
          else
            croak("Unknown mode: %s", val);
        } else if (!strcmp(key, "file")) {
          file = SvPV(ST(i+1), vlen);
        } else if (!strcmp(key, "fheader")) {
          fheaderdata = SvPV(ST(i+1), vlen);
        } else if (!strcmp(key, "fheaderlines")) {
          fheaderlines = SvIV(ST(i+1));
        } else if (!strcmp(key, "size")) {
          initial_bits = SvIV(ST(i+1));
        }
      }
    }
    RETVAL = new(mode, file, fheaderdata, fheaderlines, initial_bits);
  OUTPUT:
    RETVAL

void
DESTROY(IN Data::BitStream::XS list)

int
_maxbits()
  CODE:
    RETVAL = BITS_PER_WORD;
  OUTPUT:
    RETVAL

void
trim(IN Data::BitStream::XS list)
  CODE:
    resize(list, list->len);

UV
len(IN Data::BitStream::XS list)
  ALIAS:
    maxlen = 1
    pos = 2
  CODE:
    RETVAL = (ix == 0) ? list->len :
             (ix == 1) ? list->maxlen
                       : list->pos;
  OUTPUT:
    RETVAL

SV *
fheader(IN Data::BitStream::XS list)
  CODE:
    if (list->file_header == 0) {
      XSRETURN_UNDEF;
    } else {
      RETVAL = newSVpv(list->file_header, 0);
    }
  OUTPUT:
    RETVAL

int
_set_pos(IN Data::BitStream::XS list, IN int n)

int
_set_len(IN Data::BitStream::XS list, IN int n)

bool
writing(IN Data::BitStream::XS list)
  CODE:
    RETVAL = list->is_writing;
  OUTPUT:
    RETVAL

void
rewind(IN Data::BitStream::XS list)
  CODE:
    if (list->is_writing)
      croak("rewind while writing");
    else
      _set_pos(list, 0);

void
skip(IN Data::BitStream::XS list, IN int bits)
  CODE:
    if (list->is_writing)
      croak("skip while writing");
    else if ((list->pos + bits) > list->len)
      croak("skip off stream");
    else
      _set_pos(list, list->pos + bits);

bool
exhausted(IN Data::BitStream::XS list)
  CODE:
    if (list->is_writing)
      croak("exhausted while writing");
    RETVAL = (list->pos >= list->len);
  OUTPUT:
    RETVAL

void
erase(IN Data::BitStream::XS list)
  CODE:
    resize(list, 0);

void
read_open(IN Data::BitStream::XS list)

void
write_open(IN Data::BitStream::XS list)

void
write_close(IN Data::BitStream::XS list)

UV
read(IN Data::BitStream::XS list, IN int bits, IN const char* flags = 0)
  PREINIT:
    int do_readahead;
  CODE:
    if (list->is_writing) {
      croak("read while writing");
      XSRETURN_UNDEF;
    }
    if ( (bits <= 0) || (bits > BITS_PER_WORD) ) {
      croak("invalid parameters: bits %d must be 1-%d",bits,(int)BITS_PER_WORD);
      XSRETURN_UNDEF;
    }
    do_readahead = (flags != 0) && (strcmp(flags, "readahead") == 0);
    if (do_readahead) {
      if (list->pos >= list->len)
        XSRETURN_UNDEF;
      RETVAL = sreadahead(list, bits);
    } else {
      if ( (list->pos + bits-1) >= list->len )
        XSRETURN_UNDEF;
      RETVAL = sread(list, bits);
    }
  OUTPUT:
    RETVAL

UV
readahead(IN Data::BitStream::XS list, IN int bits)
  CODE:
    if (list->is_writing) {
      croak("read while writing");
      XSRETURN_UNDEF;
    }
    if ( (bits <= 0) || (bits > BITS_PER_WORD) ) {
      croak("invalid parameters: bits %d must be 1-%d",bits,(int)BITS_PER_WORD);
      XSRETURN_UNDEF;
    }
    if (list->pos >= list->len)
      XSRETURN_UNDEF;
    RETVAL = sreadahead(list, bits);
  OUTPUT:
    RETVAL

void
write(IN Data::BitStream::XS list, IN int bits, IN UV v)
  CODE:
    if (!list->is_writing) {
      croak("write while reading");
      XSRETURN_UNDEF;
    }
    if ( (bits <= 0) || ( (v > 1) && (bits > BITS_PER_WORD) ) ) {
      croak("invalid parameters: bits %d must be 1-%d",bits,(int)BITS_PER_WORD);
      XSRETURN_UNDEF;
    }
    swrite(list, bits, v);

void
put_string(IN Data::BitStream::XS list, ...)
  CODE:
    if (!list->is_writing) {
      croak("write while reading");
    } else {
      int c = 0;
      while (++c < items)
        put_string(list, SvPV_nolen(ST(c)));
    }

SV *
read_string(IN Data::BitStream::XS list, IN int bits)
  PREINIT:
    char* buf;
  CODE:
    if (list->is_writing)
      { croak("read while writing"); XSRETURN_UNDEF; }
    if (bits < 0)
      { croak("invalid parameters: bits %d must be >= 0",bits); XSRETURN_UNDEF;}
    if (bits > (list->len - list->pos))
      { croak("short read"); XSRETURN_UNDEF; }
    buf = read_string(list, bits);
    RETVAL = newSVpvn(buf, bits);
    Safefree(buf);
  OUTPUT:
    RETVAL

SV*
to_raw(IN Data::BitStream::XS list)
  PREINIT:
    char* buf;
    size_t bytes;
  CODE:
    buf = to_raw(list);
    bytes = NBYTES(list->len);  /* Return just the necessary number of bytes */
    RETVAL = newSVpvn(buf, bytes);
    Safefree(buf);
  OUTPUT:
    RETVAL

void
put_raw(IN Data::BitStream::XS list, IN const char* str, IN int bits)

void
from_raw(IN Data::BitStream::XS list, IN const char* str, IN int bits)

void
_xput_stream(IN Data::BitStream::XS list, IN Data::BitStream::XS source)
  CODE:
    if (!list->is_writing) {
      croak("write while reading");
    } else {
      _xput_stream(list, source);
    }


void
get_unary(IN Data::BitStream::XS list, IN int count = 1)
  ALIAS:
    get_unary1 = 1
    get_gamma = 2
    get_delta = 3
    get_omega = 4
    get_fib = 5
    get_levenstein = 6
    get_evenrodeh = 7
    get_goldbach_g1 = 8
    get_goldbach_g2 = 9
  PPCODE:
    switch (ix) {
      case 0:   GET_CODE(unary);  break;
      case 1:   GET_CODE(unary1);  break;
      case 2:   GET_CODE(gamma);  break;
      case 3:   GET_CODE(delta);  break;
      case 4:   GET_CODE(omega);  break;
      case 5:   GET_CODE(fib);  break;
      case 6:   GET_CODE(levenstein);  break;
      case 7:   GET_CODE(evenrodeh);  break;
      case 8:   GET_CODE(goldbach_g1);  break;
      case 9:
      default:  GET_CODE(goldbach_g2);  break;
    }

void
put_unary(IN Data::BitStream::XS list, ...)
  ALIAS:
    put_unary1 = 1
    put_gamma = 2
    put_delta = 3
    put_omega = 4
    put_fib = 5
    put_levenstein = 6
    put_evenrodeh = 7
    put_goldbach_g1 = 8
    put_goldbach_g2 = 9
  PREINIT:
    int c;
  CODE:
    if (!list->is_writing) croak("write while reading");
    c = 0;
    while (++c < items) {
      UV n = get_uv_from_st(ST(c));
      switch (ix) {
        case 0:   put_unary(list,n);  break;
        case 1:   put_unary1(list,n);  break;
        case 2:   put_gamma(list,n);  break;
        case 3:   put_delta(list,n);  break;
        case 4:   put_omega(list,n);  break;
        case 5:   put_fib(list,n);  break;
        case 6:   put_levenstein(list,n);  break;
        case 7:   put_evenrodeh(list,n);  break;
        case 8:   put_goldbach_g1(list,n);  break;
        case 9:
        default:  put_goldbach_g2(list,n);  break;
      }
    }

void
get_fibgen(IN Data::BitStream::XS list, IN int m, IN int count = 1)
  ALIAS:
    get_binword = 1
    get_baer = 2
    get_boldivigna = 3
    get_comma = 4
    get_gamma_rice = 5
    get_expgolomb = 25
    get_gamma_golomb = 6
    get_gammagolomb = 26
  PPCODE:
    if (ix == 25) ix = 5;
    if (ix == 26) ix = 6;
    if ( (ix == 0 && (m < 2 || m > 16)) ||
         (ix == 1 && (m <= 0 || m > BITS_PER_WORD)) ||
         (ix == 2 && (m < -32 || m > 32)) ||
         (ix == 3 && (m < 1 || m > 15)) ||
         (ix == 4 && (m < 1 || m > 16)) ||
         (ix == 5 && (m < 0 || m > BITS_PER_WORD)) ||
         (ix == 6 && m < 1) )
      croak("invalid parameters: %d\n", m);
    switch (ix) {
      case 0:   GET_CODEP(fibgen, m);  break;
      case 1:   GET_CODEP(binword, m);  break;
      case 2:   GET_CODEP(baer, m);  break;
      case 3:   GET_CODEP(boldivigna, m);  break;
      case 4:   GET_CODEP(comma, m);  break;
      case 5:   GET_CODEP(gamma_rice, m);  break;
      case 6:
      default:  GET_CODEP(gamma_golomb, m);  break;
    }

void
put_fibgen(IN Data::BitStream::XS list, IN int m, ...)
  ALIAS:
    put_binword = 1
    put_baer = 2
    put_boldivigna = 3
    put_comma = 4
    put_gamma_rice = 5
    put_expgolomb = 25
    put_gamma_golomb = 6
    put_gammagolomb = 26
  PREINIT:
    int c;
  CODE:
    if (!list->is_writing) croak("write while reading");
    if (ix == 25) ix = 5;
    if (ix == 26) ix = 6;
    if ( (ix == 0 && (m < 2 || m > 16)) ||
         (ix == 1 && (m <= 0 || m > BITS_PER_WORD)) ||
         (ix == 2 && (m < -32 || m > 32)) ||
         (ix == 3 && (m < 1 || m > 15)) ||
         (ix == 4 && (m < 1 || m > 16)) ||
         (ix == 5 && (m < 0 || m > BITS_PER_WORD)) ||
         (ix == 6 && m < 1) )
      croak("invalid parameters: %d\n", m);
    c = 1;
    while (++c < items) {
      UV n = get_uv_from_st(ST(c));
      switch (ix) {
        case 0:   put_fibgen(list,m,n);  break;
        case 1:   put_binword(list,m,n);  break;
        case 2:   put_baer(list,m,n);  break;
        case 3:   put_boldivigna(list,m,n);  break;
        case 4:   put_comma(list,m,n);  break;
        case 5:   put_gamma_rice(list,m,n);  break;
        case 6:
        default:  put_gamma_golomb(list,m,n);  break;
      }
    }


void
get_blocktaboo(IN Data::BitStream::XS list, IN const char* taboostr, IN int count = 1)
  PREINIT:
    int k;
    UV  taboo;
  PPCODE:
    k = parse_binary_string(taboostr, &taboo);
    if ( (k < 1) || (k > 16) ) {
      croak("invalid parameters: block taboo %s", taboostr);
      XSRETURN_UNDEF;
    }
    GET_CODEPP(block_taboo, k, taboo);

void
put_blocktaboo(IN Data::BitStream::XS list, IN const char* taboostr, ...)
  PREINIT:
    int k;
    UV  taboo;
  CODE:
    k = parse_binary_string(taboostr, &taboo);
    if ( (k < 1) || (k > 16) ) {
      croak("invalid parameters: block taboo %s", taboostr);
      return;
    }
    /* We've turned one argument into two */
    PUT_CODEVP(block_taboo, 1, list, k, taboo);

void
_xget_rice_sub(IN Data::BitStream::XS list, IN SV* coderef, IN int k, IN int count = 1)
  PREINIT:
    SV* self = ST(0);
    SV* cref = 0;
  PPCODE:
    if ( (k < 0) || (k > BITS_PER_WORD) ) {
      croak("invalid parameters: rice %d", k);
      XSRETURN_UNDEF;
    }
    if (!SvROK(coderef)) {
      self = 0;
      cref = 0;
    } else {
      if ((!SvROK(coderef)) || (SvTYPE(SvRV(coderef)) != SVt_PVCV) ) {
        croak("invalid parameters: rice coderef");
        return;
      }
      cref = SvRV(coderef);
    }
    GET_CODESPP(rice_sub, self, cref, k);

void
_xput_rice_sub(IN Data::BitStream::XS list, IN SV* coderef, IN int k, ...)
  PREINIT:
    SV* self = ST(0);
    SV* cref = 0;
  CODE:
    if ( (k < 0) || (k > BITS_PER_WORD) ) {
      croak("invalid parameters: rice %d", k);
      return;
    }
    if (!SvROK(coderef)) {
      self = 0;
      cref = 0;
    } else {
      if ((!SvROK(coderef)) || (SvTYPE(SvRV(coderef)) != SVt_PVCV) ) {
        croak("invalid parameters: rice coderef");
        return;
      }
      cref = SvRV(coderef);
    }
    PUT_CODESPP(rice_sub, self, cref, k);

void
_xget_golomb_sub(IN Data::BitStream::XS list, IN SV* coderef, IN UV m, IN int count = 1)
  PREINIT:
    SV* self = ST(0);
    SV* cref = 0;
  PPCODE:
    if (m < W_ONE) {
      croak("invalid parameters: golomb %lu", m);
      XSRETURN_UNDEF;
    }
    if (!SvROK(coderef)) {
      self = 0;
      cref = 0;
    } else {
      if ((!SvROK(coderef)) || (SvTYPE(SvRV(coderef)) != SVt_PVCV) ) {
        croak("invalid parameters: golomb coderef");
        return;
      }
      cref = SvRV(coderef);
    }
    GET_CODESPP(golomb_sub, self, cref, m);

void
_xput_golomb_sub(IN Data::BitStream::XS list, IN SV* coderef, IN UV m, ...)
  PREINIT:
    SV* self = ST(0);
    SV* cref = 0;
  CODE:
    if (m < W_ONE) {
      croak("invalid parameters: golomb %lu", m);
      return;
    }
    if (!SvROK(coderef)) {
      self = 0;
      cref = 0;
    } else {
      if ((!SvROK(coderef)) || (SvTYPE(SvRV(coderef)) != SVt_PVCV) ) {
        croak("invalid parameters: golomb coderef");
        return;
      }
      cref = SvRV(coderef);
    }
    PUT_CODESPP(golomb_sub, self, cref, m);


void
_xget_arice_sub(list, coderef, k, count=1)
      Data::BitStream::XS list
      SV* coderef
      int &k
      int count
  PREINIT:
    SV* self = ST(0);
    SV* cref = 0;
    SV* stack_k_ptr = ST(2);  /* Remember position of k, it will be modified */
  PPCODE:
    if ( (k < 0) || (k > BITS_PER_WORD) ) {
      croak("invalid parameters: adaptive_rice %d", k);
      XSRETURN_UNDEF;
    }
    if (!SvROK(coderef)) {
      self = 0;
      cref = 0;
    } else {
      if ((!SvROK(coderef)) || (SvTYPE(SvRV(coderef)) != SVt_PVCV) ) {
        croak("invalid parameters: adaptive_rice coderef");
        return;
      }
      cref = SvRV(coderef);
    }
    GET_CODESPP(adaptive_rice_sub, self, cref, &k);
    /* Return the modified k back to Perl */
    sv_setiv(stack_k_ptr, k);
    SvSETMAGIC(stack_k_ptr);

void
_xput_arice_sub(list, coderef, k, ...)
      Data::BitStream::XS list
      SV* coderef
      int &k
  PREINIT:
    SV* self = ST(0);
    SV* cref = 0;
  CODE:
    if ( (k < 0) || (k > BITS_PER_WORD) ) {
      croak("invalid parameters: adaptive_rice %d", k);
      return;
    }
    if (!SvROK(coderef)) {
      self = 0;
      cref = 0;
    } else {
      if ((!SvROK(coderef)) || (SvTYPE(SvRV(coderef)) != SVt_PVCV) ) {
        croak("invalid parameters: adaptive_rice coderef");
        return;
      }
      cref = SvRV(coderef);
    }
    PUT_CODESPP(adaptive_rice_sub, self, cref, &k);
  OUTPUT:
    k


void
get_startstop(IN Data::BitStream::XS list, IN SV* p, IN int count = 1)
  PREINIT:
    char* map;
  PPCODE:
    map = make_startstop_prefix_map(p);
    if (map == 0) {
      XSRETURN_UNDEF;
    }
    /* TODO: we'll skip free in some croak conditions */
    GET_CODEP(startstop, map);
    Safefree(map);

void
put_startstop(IN Data::BitStream::XS list, IN SV* p, ...)
  PREINIT:
    char* map;
  CODE:
    map = make_startstop_prefix_map(p);
    if (map == 0)
       return;
    PUT_CODEVP(startstop, 1, list, map);
    Safefree(map);


void prime_init(IN UV n)

UV prime_count(IN UV n)

UV nth_prime(IN UV n)

int is_prime(IN UV n)