#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "standard.h"
#include "rand.h"

#include <stdlib.h>
#include <string.h>
#include <assert.h>


struct session_token_ctx {
  int mask;
  int count;
  int curr_word;
  int bytes_left_in_curr_word;
  struct randctx isaac_ctx;
  char *alphabet;
  size_t alphabet_length;
  size_t token_length;
};

typedef struct session_token_ctx * Session_Token;

static inline int get_new_byte(struct session_token_ctx *ctx) {
  int output;

  if (ctx->bytes_left_in_curr_word == 0) {
    if (ctx->count > 255) {
      isaac(&ctx->isaac_ctx);
      ctx->count = 0;
    }

    ctx->curr_word = ctx->isaac_ctx.randrsl[ctx->count];
    ctx->count++;
    ctx->bytes_left_in_curr_word = 4;
  }

  output = ctx->curr_word & 0xFF;
  ctx->bytes_left_in_curr_word--;
  ctx->curr_word >>= 8;

  return output;
}

static int get_mask(int v) {
  v--;
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  return v;
}


MODULE = Session::Token		PACKAGE = Session::Token		

PROTOTYPES: ENABLE


Session_Token
_new_context(seed, alphabet, token_length)
        SV *seed
        SV *alphabet
        size_t token_length
    CODE:
        struct session_token_ctx *ctx;
        char *seedp;
        size_t len;

        len = SvCUR(seed);
        seedp = SvPV(seed, len);

        assert(sizeof(ctx->isaac_ctx.randrsl) == 1024);

        if (len != 1024) {
          croak("unexpected seed length: %lu", len);
        }

        ctx = malloc(sizeof(struct session_token_ctx));
        memset(ctx, '\0', sizeof(struct session_token_ctx));

        memcpy(&ctx->isaac_ctx.randrsl, seedp, 1024);
        randinit(&ctx->isaac_ctx, TRUE);
        isaac(&ctx->isaac_ctx);

        ctx->alphabet_length = SvCUR(alphabet);
        ctx->alphabet = malloc(ctx->alphabet_length);
        memcpy(ctx->alphabet, SvPV(alphabet, ctx->alphabet_length), ctx->alphabet_length);

        ctx->token_length = token_length;

        ctx->mask = get_mask(ctx->alphabet_length);

        RETVAL = ctx;

    OUTPUT:
        RETVAL


void
DESTROY(ctx)
        Session_Token ctx
    CODE:
        free(ctx->alphabet);
        free(ctx);


SV *
get(ctx)
        Session_Token ctx
    CODE:
        SV *output;
        char *outputp;
        size_t i, curr;

        output = newSVpvn("", 0);
        SvGROW(output, ctx->token_length);
        SvCUR_set(output, ctx->token_length);
        outputp = SvPV(output, ctx->token_length);

        for (i=0; i<ctx->token_length; i++) {
          while((curr = (get_new_byte(ctx) & ctx->mask)) >= ctx->alphabet_length)  ;
          outputp[i] = ctx->alphabet[curr];
        }

        RETVAL = output;

    OUTPUT:
        RETVAL

