/* ISAAC PRNG, 1996, Public Domain.
 * Authored by Bob Jenkins <URL:mailto:bob_jenkins@burtleburtle.net>
 *
 * More information at <URL:http://burtleburtle.net/bob/rand/isaacafa.html>
 * 
 * 
 * 2001-12-15  Adapted as a drop-in replacement for CircleMUD's random.c
 *             by Kram @ Iconoclast <URL:telnet://iconoclast.org:7777/>
 */

/* Note: All comments and non-circle portions of this code were
 * written by Bob Jenkins. I was inspired to adapt it as a
 * replacement for stock Circle's PRNG after playing around with
 * the DIEHARD RNG test suite.
 *   -- Kram
 */

typedef unsigned long int ub4;	/* unsigned 4-byte quantities */
typedef unsigned char ub1;
typedef int word;		/* fastest type available */

#define RANDSIZL   (8)		/* I recommend 8 for crypto, 4 for simulations */
#define RANDSIZ    (1<<RANDSIZL)

/* context of random number generator */
struct randctx {
  ub4 randcnt;
  ub4 randrsl[RANDSIZ];
  ub4 randmem[RANDSIZ];
  ub4 randa;
  ub4 randb;
  ub4 randc;
};
typedef struct randctx randctx;


#define ind(mm,x)  (*(ub4 *)((ub1 *)(mm) + ((x) & ((RANDSIZ-1)<<2))))
#define rngstep(mix,a,b,mm,m,m2,r,x) \
{ \
  x = *m;  \
  a = (a^(mix)) + *(m2++); \
  *(m++) = y = ind(mm,x) + a + b; \
  *(r++) = b = ind(mm,y>>RANDSIZL) + x; \
}

void
isaac (randctx * ctx)
{
  register ub4 a, b, x, y, *m, *mm, *m2, *r, *mend;
  mm = ctx->randmem;
  r = ctx->randrsl;
  a = ctx->randa;
  b = ctx->randb + (++ctx->randc);
  for (m = mm, mend = m2 = m + (RANDSIZ / 2); m < mend;) {
    rngstep (a << 13, a, b, mm, m, m2, r, x);
    rngstep (a >> 6, a, b, mm, m, m2, r, x);
    rngstep (a << 2, a, b, mm, m, m2, r, x);
    rngstep (a >> 16, a, b, mm, m, m2, r, x);
  }
  for (m2 = mm; m2 < mend;) {
    rngstep (a << 13, a, b, mm, m, m2, r, x);
    rngstep (a >> 6, a, b, mm, m, m2, r, x);
    rngstep (a << 2, a, b, mm, m, m2, r, x);
    rngstep (a >> 16, a, b, mm, m, m2, r, x);
  }
  ctx->randb = b;
  ctx->randa = a;
}


#define mix(a,b,c,d,e,f,g,h) \
{ \
   a^=b<<11; d+=a; b+=c; \
   b^=c>>2;  e+=b; c+=d; \
   c^=d<<8;  f+=c; d+=e; \
   d^=e>>16; g+=d; e+=f; \
   e^=f<<10; h+=e; f+=g; \
   f^=g>>4;  a+=f; g+=h; \
   g^=h<<8;  b+=g; h+=a; \
   h^=a>>9;  c+=h; a+=b; \
}

void
randinit (randctx * ctx)
{
  word i;
  ub4 a, b, c, d, e, f, g, h;
  ub4 *m, *r;
  ctx->randa = ctx->randb = ctx->randc = 0;
  m = ctx->randmem;
  r = ctx->randrsl;
  a = b = c = d = e = f = g = h = 0x9e3779b9;	/* the golden ratio */

  for (i = 0; i < 4; ++i) {	/* scramble it */
    mix (a, b, c, d, e, f, g, h);
  }

  /* initialize using the contents of r[] as the seed */
  for (i = 0; i < RANDSIZ; i += 8) {
    a += r[i];
    b += r[i + 1];
    c += r[i + 2];
    d += r[i + 3];
    e += r[i + 4];
    f += r[i + 5];
    g += r[i + 6];
    h += r[i + 7];
    mix (a, b, c, d, e, f, g, h);
    m[i] = a;
    m[i + 1] = b;
    m[i + 2] = c;
    m[i + 3] = d;
    m[i + 4] = e;
    m[i + 5] = f;
    m[i + 6] = g;
    m[i + 7] = h;
  }
  /* do a second pass to make all of the seed affect all of m */
  for (i = 0; i < RANDSIZ; i += 8) {
    a += m[i];
    b += m[i + 1];
    c += m[i + 2];
    d += m[i + 3];
    e += m[i + 4];
    f += m[i + 5];
    g += m[i + 6];
    h += m[i + 7];
    mix (a, b, c, d, e, f, g, h);
    m[i] = a;
    m[i + 1] = b;
    m[i + 2] = c;
    m[i + 3] = d;
    m[i + 4] = e;
    m[i + 5] = f;
    m[i + 6] = g;
    m[i + 7] = h;
  }

  isaac (ctx);			/* fill in the first set of results */
  ctx->randcnt = RANDSIZ;	/* prepare to use the first set of results */
}

static randctx ctx;

void
circle_srandom (unsigned long initial_seed)
{
  int i;

  ctx.randa = ctx.randb = ctx.randc = (ub4) 0;

  for (i = 0; i < 256; ++i)
    ctx.randrsl[i] = initial_seed;

  randinit (&ctx);
}

unsigned long
circle_random (void)
{
  ctx.randcnt--;
  if (!ctx.randcnt) {
    isaac (&ctx);
    ctx.randcnt = RANDSIZ - 1;
  }

  return (unsigned long) ctx.randrsl[ctx.randcnt];
}

