// Ranlux24. (1.01)
// These are probably too clunky to be generally useful.
// @todo Consolidate to a single class.

#include <stdlib.h>
#include <limits.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>

// Utility.

#define unlikely(c) \
    __builtin_expect(!!(c), 0)

#define BITS(x) \
    (sizeof(x) * CHAR_BIT)

#define NEW_T(T, count) \
    ((T *) allocate(count * sizeof(T)))

void *allocate(size_t n)
{
    void *r = malloc(n);
    assert(r != 0 || n == 0);
    return r;
}

// EngineBase.

typedef struct EngineBase {
    int  (*next)    (struct EngineBase*);
    void (*discard) (struct EngineBase*, int);
    void (*delete)  (struct EngineBase*);
} EngineBase;

int nextEngineBase(EngineBase *self)
{
    return self->next(self);
}

void discardEngineBase(EngineBase *self, int n)
{
    return self->discard(self, n);
}

void deleteEngineBase(EngineBase *self)
{
    return self->delete(self);
}

// SubtractWithCarry.

typedef struct {
    EngineBase base;
    int *x, c, i, m, r, s;
} SubtractWithCarryEngine;

static inline int next_(int *x, int mask, int i_s, int i_r, int *carry)
{
    int y = x[i_s] - x[i_r] - *carry;
    *carry = -(y >> (BITS(int)-1));
    return y & mask;
}

int nextSubtractWithCarryEngine(EngineBase *base)
{
    SubtractWithCarryEngine *self = (SubtractWithCarryEngine *) base;

    self->i = self->i + 1;
    int *x = self->x, i = self->i, r = self->r;

    if (unlikely(i >= r))
    {
        int *c = &self->c, m = self->m, s = self->s, t = r - s;

        for (int i = 0; i < s; i++)
            x[i] = next_(x, m, i + t, i, c);
        for (int i = s; i < r; i++)
            x[i] = next_(x, m, i - s, i, c);
        self->i = i = 0;
    }
    return x[i];
}

void discardSubtractWithCarryEngine(EngineBase *base, int n)
{
    for (int i = 0; i < n; i++)
        nextSubtractWithCarryEngine(base);
}

void deleteSubtractWithCarryEngine(EngineBase *base)
{
    SubtractWithCarryEngine *self = (SubtractWithCarryEngine *) base;

    if (self != 0)
    {
        free(self->x);
        free(self);
    }
}

EngineBase *newSubtractWithCarryEngine(int w, int s, int r)
{
    assert(0 < w && w < BITS(int));
    assert(0 < s && s < r);

    SubtractWithCarryEngine *self = NEW_T(SubtractWithCarryEngine, 1);

    self->base.next = nextSubtractWithCarryEngine;
    self->base.discard = discardSubtractWithCarryEngine;
    self->base.delete = deleteSubtractWithCarryEngine;
    self->x = NEW_T(int, r);
    self->c = 0;
    self->i = r-1;
    self->m = -1U >> (BITS(int) - w);
    self->r = r;
    self->s = s;

    for (int i = 0; i < r; i++)
        self->x[i] = rand() & self->m;
    return (EngineBase *) self;
}

// DiscardBlock.

typedef struct {
    EngineBase base, *owned;
    int p, r, i;
} DiscardBlockEngine;

int nextDiscardBlockEngine(EngineBase *base)
{
    DiscardBlockEngine *self = (DiscardBlockEngine *) base;

    if (self->i == 0)
    {
        discardEngineBase(self->owned, self->p - self->r);
        self->i = self->r;
    }
    self->i -= 1;
    return nextEngineBase(self->owned);
}

void deleteDiscardBlockEngine(EngineBase *base)
{
    DiscardBlockEngine *self = (DiscardBlockEngine *) base;

    if (self != 0)
    {
        deleteEngineBase(self->owned);
        free(self);
    }
}

EngineBase *newDiscardBlockEngine(EngineBase **unique, int p, int r)
{
    assert(0 < r <= p);

    DiscardBlockEngine *self = NEW_T(DiscardBlockEngine, 1);

    self->base.next = nextDiscardBlockEngine;
    self->base.discard = 0;
    self->base.delete = deleteDiscardBlockEngine;
    self->owned = *unique; *unique = 0; // Transfer ownership.
    self->p = p;
    self->r = r;
    self->i = r;
    return (EngineBase *) self;
}

// Ranlux24.

EngineBase *newRanlux24(void)
{
    EngineBase *ranlux24_base = newSubtractWithCarryEngine(24, 10, 24);
    return newDiscardBlockEngine(&ranlux24_base, 223, 23);
}

int main(void)
{
    srand(time(0));
    EngineBase *ranlux24 = newRanlux24();

    for (int i = 0; i < 24; i++)
    {
        int r = nextEngineBase(ranlux24);
        printf("%d\n", r);
    }
    deleteEngineBase(ranlux24);
    return 0;
}