#include "cell.h"
#include <stdint.h>

// Random number generation constants for MT19937-64
#define STATE_VECTOR_LENGTH 312
#define STATE_VECTOR_M      156
#define NN STATE_VECTOR_LENGTH
#define MM STATE_VECTOR_M
#define MATRIX_A 0xB5026F5AA96619E9ULL
#define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */
#define LM 0x7FFFFFFFULL /* Least significant 31 bits */

typedef struct tagMTRand {
  uint64_t mt[STATE_VECTOR_LENGTH];
  int32_t index;
} MTRand;

// Random number generation functions
static void m_seedRand(MTRand* rand, uint64_t seed) {
  rand->mt[0] = seed;
  for(rand->index = 1; rand->index < NN; rand->index++) {
    rand->mt[rand->index] = (6364136223846793005ULL * (rand->mt[rand->index-1] ^ (rand->mt[rand->index-1] >> 62)) + rand->index);
  }
}

static int64_t genRandLong(MTRand* rand) {
  int i;
  uint64_t x;
  static uint64_t mag01[2] = {0ULL, MATRIX_A};

  if (rand->index >= NN) { /* generate NN words at one time */
    /* if init_genrand64() has not been called, */
    /* a default initial seed is used */
    if (rand->index == NN+1) 
      m_seedRand(rand, 5489ULL); 

    for (i = 0; i < NN-MM; i++) {
      x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
      rand->mt[i] = rand->mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
    }
    for (; i < NN-1; i++) {
      x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
      rand->mt[i] = rand->mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
    }
    x = (rand->mt[NN-1] & UM) | (rand->mt[0] & LM);
    rand->mt[NN-1] = rand->mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)];

    rand->index = 0;
  }
  
  x = rand->mt[rand->index++];

  x ^= (x >> 29) & 0x5555555555555555ULL;
  x ^= (x << 17) & 0x71D67FFFEDA60000ULL;
  x ^= (x << 37) & 0xFFF7EEE000000000ULL;
  x ^= (x >> 43);

  return (int64_t)(x & 0x000FFFFFFFFFFFFFULL); /* return 52-bit value safe for JS */
}

static double genRand(MTRand* rand) {
  /* generates a random number on [0,1)-real-interval */
  return (genRandLong(rand) >> 11) * (1.0/9007199254740992.0);
}

/* JS Class Definition */
static JSClassID js_mersenne_class_id;

static void js_mersenne_finalizer(JSRuntime *rt, JSValue val) {
  MTRand *mrand = JS_GetOpaque(val, js_mersenne_class_id);
  js_free_rt(mrand);
}

static JSClassDef js_mersenne_class = {
  "Mersenne",
  .finalizer = js_mersenne_finalizer,
};

static MTRand *js2mersenne(JSContext *js, JSValue v) {
  return JS_GetOpaque(v, js_mersenne_class_id);
}

/* Methods */
JSC_CCALL(mersenne_get,
  MTRand *mrand = js2mersenne(js, self);
  if (!mrand) return JS_ThrowTypeError(js, "Invalid mersenne context");
  return JS_NewFloat64(js, genRand(mrand));
)

static const JSCFunctionListEntry js_mersenne_funcs[] = {
  JS_CFUNC_DEF("get", 0, js_mersenne_get),
};

/* Factory Function (constructor-style, see js_blob_constructor in qjs_blob.c) */
static JSValue js_mersenne_use_call(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv)
{
  uint64_t seed;
  
  if (argc == 0 || JS_IsNull(argv[0])) {
    // Use OS random
    extern int randombytes(void *buf, size_t n);
    randombytes(&seed, 8);
  } else {
    if (JS_ToFloat64(js, (double*)&seed, argv[0])) {
       // Fallback to number if bigint fails or is not provided as bigint
       double d;
       if (JS_ToFloat64(js, &d, argv[0])) return JS_EXCEPTION;
       seed = (uint64_t)d;
    }
  }

  MTRand *mrand = js_malloc_rt(sizeof(MTRand));
  if (!mrand) return JS_ThrowOutOfMemory(js);
  
  m_seedRand(mrand, seed);
  
  JS_FRAME(js);
  JS_ROOT(obj, JS_NewObjectClass(js, js_mersenne_class_id));
  if (JS_IsException(obj.val)) {
    js_free_rt(mrand);
    JS_RETURN_EX();
  }

  JS_SetOpaque(obj.val, mrand);

  // Store seed as a read-only property
  JS_SetPropertyStr(js, obj.val, "seed", JS_NewFloat64(js, seed));

  JS_RETURN(obj.val);
}

CELL_USE_INIT(
  JS_NewClassID(&js_mersenne_class_id);
  JS_NewClass(js, js_mersenne_class_id, &js_mersenne_class);

  JS_FRAME(js);
  JS_ROOT(proto, JS_NewObject(js));
  JS_SetPropertyFunctionList(js, proto.val, js_mersenne_funcs, sizeof(js_mersenne_funcs)/sizeof(JSCFunctionListEntry));
  JS_SetClassProto(js, js_mersenne_class_id, proto.val);

  // Return the factory function
  JS_RETURN(JS_NewCFunction2(js, js_mersenne_use_call, "mersenne", 1, JS_CFUNC_generic, 0));
)
