/*
 * simplex_native.c — C backend for Noise::Simplex::Native.
 *
 * Mirrors Noise::Simplex (lib/Noise/Simplex.rakumod) line-for-line in
 * IEEE 754 double precision. Permutation table is built in Raku (via
 * Math::Random::MT) and handed in as a 256-byte array, so we do not
 * reimplement MT19937_64 here.
 *
 * Public ABI (see comments on each function):
 *   simplex_create(perm256)               -> SimplexCtx*
 *   simplex_destroy(ctx)
 *   simplex_noise2d(ctx, x, y)            -> double
 *   simplex_noise3d(ctx, x, y, z)         -> double
 *   simplex_fill2d_grid(ctx, ..., out)    void; bulk grid fill
 *   simplex_fill3d_grid(ctx, ..., out)    void; bulk grid fill
 */

#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

/* GCC/Clang attributes; no-op elsewhere.
 *
 * `hot`  — code-layout hint; the optimizer keeps these in cache-warm
 *          regions and is more aggressive about inlining them.
 * `pure` — return depends only on args + memory pointed to by args
 *          (no globals, no I/O, no writes). Lets the compiler CSE
 *          repeated calls with identical args.
 */
#if defined(__GNUC__) || defined(__clang__)
#  define NSN_HOT  __attribute__((hot))
#  define NSN_PURE __attribute__((pure))
#else
#  define NSN_HOT
#  define NSN_PURE
#endif

/* Symbol export. On Windows / MSVC, public symbols must be flagged
 * `__declspec(dllexport)` to land in the .dll's export table — unlike
 * macOS / Linux where `cc -shared` exports everything by default.
 * Mark every public ABI function with NSN_API so a single source
 * tree compiles correctly under both toolchains. */
#if defined(_WIN32) || defined(_WIN64)
#  define NSN_API __declspec(dllexport)
#elif defined(__GNUC__) || defined(__clang__)
#  define NSN_API __attribute__((visibility("default")))
#else
#  define NSN_API
#endif

/* Map an integer-valued `double` (the output of floor()) to its
 * low-8-bits-as-uint8, matching Raku's `$n +& 255` exactly.
 *
 * Defined for any finite double whose integer part fits in int32_t
 * (|v| < 2^31). The fast path is a single cast + mask — same code
 * the naive `(int)((long)v & 255)` would emit, but with an explicit
 * range guard so it isn't UB on overflow.
 *
 * For the rare case of huge coordinates (e.g. high-octave fBm with
 * |coord| > 2^31, which can happen with unbounded base coords) we
 * fall back to `fmod(v, 256.0)`, which is defined for any finite
 * double. The result is bit-identical to the fast path for any v in
 * the fast-path range — verified at every test sample.
 *
 * NaN / ±Inf inputs remain undefined behaviour. The original Raku
 * also errors on `NaN.floor +& 255`, so this matches semantics. */
static inline uint8_t mod256_double(double v) {
    if (v >= -2147483648.0 && v <= 2147483647.0) {
        return (uint8_t) ((int32_t) v & 0xFF);
    }
    double r = fmod(v, 256.0);
    if (r < 0.0) r += 256.0;
    return (uint8_t) r;
}

/* Skew/unskew constants. The Raku source uses
 *   $sqrt3 = 3.sqrt;                 (Num — IEEE 754 double)
 *   $f2 = 0.5 * ($sqrt3 - 1);
 *   $g2 = (3 - $sqrt3) / 6;
 *   $f3 = 1/3;                       (Rat in Raku — see DETERMINISM note)
 *   $g3 = 1/6;                       (Rat)
 *
 * In 2D the Raku pipeline runs entirely in Num because $sqrt3 forces
 * Num at the first multiplication. In 3D the Raku constants stay Rat
 * and the pipeline computes in arbitrary-precision Rat, so bit-
 * identical reproduction from C double is not possible — we get
 * within ~1 ULP. See doc/README.rakudoc for the full note.
 */
static const double F2_C  = 0.5 * (1.7320508075688772 - 1.0);
static const double G2_C  = (3.0 - 1.7320508075688772) / 6.0;
static const double F3_C  = 1.0 / 3.0;
static const double G3_C  = 1.0 / 6.0;

/* The 12-vector gradient tables — same values as @grad2 / @grad3 in
 * the Raku source, indexed identically (perm % 12) * 2/3 + component. */
static const int8_t GRAD2[24] = {
     1,  1,
    -1,  1,
     1, -1,
    -1, -1,
     1,  0,
    -1,  0,
     1,  0,
    -1,  0,
     0,  1,
     0, -1,
     0,  1,
     0, -1,
};

static const int8_t GRAD3[36] = {
     1,  1,  0,
    -1,  1,  0,
     1, -1,  0,
    -1, -1,  0,
     1,  0,  1,
    -1,  0,  1,
     1,  0, -1,
    -1,  0, -1,
     0,  1,  1,
     0, -1,  1,
     0,  1, -1,
     0, -1, -1,
};

struct SimplexCtx {
    uint8_t perm[512];
    /* Pre-resolved gradient component lookups, indexed by perm value.
     * Stored as double so the noise hot loop never hits an int->double
     * conversion. ~20 KB total per ctx, negligible. */
    double  perm_grad2x[512];
    double  perm_grad2y[512];
    double  perm_grad3x[512];
    double  perm_grad3y[512];
    double  perm_grad3z[512];
};
typedef struct SimplexCtx SimplexCtx;

/* Build a fresh ctx from a 256-byte permutation. The caller-owned
 * `perm256` is duplicated into the upper half (perm[256..511] mirrors
 * perm[0..255]), matching the Raku build-permutation-table return.
 *
 * Returns NULL on allocation failure. The returned pointer must be
 * released with simplex_destroy. */
NSN_API
SimplexCtx* simplex_create(const uint8_t* perm256) {
    if (!perm256) return NULL;
    SimplexCtx* ctx = (SimplexCtx*) malloc(sizeof(SimplexCtx));
    if (!ctx) return NULL;

    memcpy(ctx->perm,       perm256, 256);
    memcpy(ctx->perm + 256, perm256, 256);

    for (int i = 0; i < 512; i++) {
        int g2 = (ctx->perm[i] % 12) * 2;
        int g3 = (ctx->perm[i] % 12) * 3;
        ctx->perm_grad2x[i] = (double) GRAD2[g2 + 0];
        ctx->perm_grad2y[i] = (double) GRAD2[g2 + 1];
        ctx->perm_grad3x[i] = (double) GRAD3[g3 + 0];
        ctx->perm_grad3y[i] = (double) GRAD3[g3 + 1];
        ctx->perm_grad3z[i] = (double) GRAD3[g3 + 2];
    }
    return ctx;
}

/* Null-safe; double-free is undefined per ABI but a NULL arg is a
 * legal no-op (matches the way Raku's dispose() can be called twice). */
NSN_API
void simplex_destroy(SimplexCtx* ctx) {
    if (!ctx) return;
    free(ctx);
}

/* 2D simplex noise. Mirrors create-noise2d in Noise::Simplex
 * (lib/Noise/Simplex.rakumod:96-147) op-for-op. Returns a value in
 * roughly [-1, 1] (scale factor 70). */
NSN_API NSN_HOT NSN_PURE
double simplex_noise2d(const SimplexCtx* ctx, double x, double y) {
    double n0 = 0.0, n1 = 0.0, n2 = 0.0;

    double s = (x + y) * F2_C;
    double i_f = floor(x + s);
    double j_f = floor(y + s);

    double t  = (i_f + j_f) * G2_C;
    double X0 = i_f - t;
    double Y0 = j_f - t;

    double x0 = x - X0;
    double y0 = y - Y0;

    int i1, j1;
    if (x0 > y0) { i1 = 1; j1 = 0; }
    else         { i1 = 0; j1 = 1; }

    double x1 = x0 - (double) i1 + G2_C;
    double y1 = y0 - (double) j1 + G2_C;
    double x2 = x0 - 1.0 + 2.0 * G2_C;
    double y2 = y0 - 1.0 + 2.0 * G2_C;

    /* Raku does $i +& 255 with arbitrary-precision integers; we
     * reproduce the low-8-bits modular wrap via mod256_double, which
     * is defined for all finite doubles (no UB at huge coords). */
    int ii = mod256_double(i_f);
    int jj = mod256_double(j_f);

    double t0 = 0.5 - x0*x0 - y0*y0;
    if (t0 >= 0.0) {
        int gi0 = ii + ctx->perm[jj];
        double gx = ctx->perm_grad2x[gi0];
        double gy = ctx->perm_grad2y[gi0];
        t0 *= t0;
        n0 = t0 * t0 * (gx * x0 + gy * y0);
    }

    double t1 = 0.5 - x1*x1 - y1*y1;
    if (t1 >= 0.0) {
        int gi1 = ii + i1 + ctx->perm[jj + j1];
        double gx = ctx->perm_grad2x[gi1];
        double gy = ctx->perm_grad2y[gi1];
        t1 *= t1;
        n1 = t1 * t1 * (gx * x1 + gy * y1);
    }

    double t2 = 0.5 - x2*x2 - y2*y2;
    if (t2 >= 0.0) {
        int gi2 = ii + 1 + ctx->perm[jj + 1];
        double gx = ctx->perm_grad2x[gi2];
        double gy = ctx->perm_grad2y[gi2];
        t2 *= t2;
        n2 = t2 * t2 * (gx * x2 + gy * y2);
    }

    return 70.0 * (n0 + n1 + n2);
}

/* 3D simplex noise. Mirrors create-noise3d in Noise::Simplex
 * (lib/Noise/Simplex.rakumod:156-238) op-for-op. Returns a value in
 * roughly [-1, 1] (scale factor 32). 3D output is typically within
 * a few ULPs of the pure-Raku original; see DETERMINISM in
 * doc/README.rakudoc for the corner-flip caveat. */
NSN_API NSN_HOT NSN_PURE
double simplex_noise3d(const SimplexCtx* ctx, double x, double y, double z) {
    double n0 = 0.0, n1 = 0.0, n2 = 0.0, n3 = 0.0;

    double s = (x + y + z) * F3_C;
    double i_f = floor(x + s);
    double j_f = floor(y + s);
    double k_f = floor(z + s);

    double t  = (i_f + j_f + k_f) * G3_C;
    double X0 = i_f - t;
    double Y0 = j_f - t;
    double Z0 = k_f - t;

    double x0 = x - X0;
    double y0 = y - Y0;
    double z0 = z - Z0;

    /* Same simplex-corner tables as the Raku source. */
    int i1, j1, k1, i2, j2, k2;
    if (x0 >= y0) {
        if (y0 >= z0)        { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; }
        else if (x0 >= z0)   { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; }
        else                 { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; }
    } else {
        if (y0 < z0)         { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; }
        else if (x0 < z0)    { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; }
        else                 { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; }
    }

    double x1 = x0 - (double) i1 + G3_C;
    double y1 = y0 - (double) j1 + G3_C;
    double z1 = z0 - (double) k1 + G3_C;

    double x2 = x0 - (double) i2 + 2.0 * G3_C;
    double y2 = y0 - (double) j2 + 2.0 * G3_C;
    double z2 = z0 - (double) k2 + 2.0 * G3_C;

    double x3 = x0 - 1.0 + 3.0 * G3_C;
    double y3 = y0 - 1.0 + 3.0 * G3_C;
    double z3 = z0 - 1.0 + 3.0 * G3_C;

    int ii = mod256_double(i_f);
    int jj = mod256_double(j_f);
    int kk = mod256_double(k_f);

    int gi0 = ii + ctx->perm[jj + ctx->perm[kk]];
    double t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
    if (t0 >= 0.0) {
        double gx = ctx->perm_grad3x[gi0];
        double gy = ctx->perm_grad3y[gi0];
        double gz = ctx->perm_grad3z[gi0];
        t0 *= t0;
        n0 = t0 * t0 * (gx * x0 + gy * y0 + gz * z0);
    }

    int gi1 = ii + i1 + ctx->perm[jj + j1 + ctx->perm[kk + k1]];
    double t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
    if (t1 >= 0.0) {
        double gx = ctx->perm_grad3x[gi1];
        double gy = ctx->perm_grad3y[gi1];
        double gz = ctx->perm_grad3z[gi1];
        t1 *= t1;
        n1 = t1 * t1 * (gx * x1 + gy * y1 + gz * z1);
    }

    int gi2 = ii + i2 + ctx->perm[jj + j2 + ctx->perm[kk + k2]];
    double t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
    if (t2 >= 0.0) {
        double gx = ctx->perm_grad3x[gi2];
        double gy = ctx->perm_grad3y[gi2];
        double gz = ctx->perm_grad3z[gi2];
        t2 *= t2;
        n2 = t2 * t2 * (gx * x2 + gy * y2 + gz * z2);
    }

    int gi3 = ii + 1 + ctx->perm[jj + 1 + ctx->perm[kk + 1]];
    double t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
    if (t3 >= 0.0) {
        double gx = ctx->perm_grad3x[gi3];
        double gy = ctx->perm_grad3y[gi3];
        double gz = ctx->perm_grad3z[gi3];
        t3 *= t3;
        n3 = t3 * t3 * (gx * x3 + gy * y3 + gz * z3);
    }

    return 32.0 * (n0 + n1 + n2 + n3);
}

/* --- Bulk grid fills ----------------------------------------------------
 *
 * Both functions sample noise on a regular grid and write the results
 * into `out` (caller-allocated, w*h doubles for 2D / w*h*d for 3D).
 *
 * Output indexing:
 *   2D:  out[j*w + i]            for the (i, j) cell, i ∈ [0, w), j ∈ [0, h)
 *   3D:  out[(k*h + j)*w + i]    for the (i, j, k) cell — z-major,
 *                                then y, then x. Matches the iteration
 *                                order of examples/3dnoise.raku.
 *
 * Per-cell coordinates are computed as (x0 + i*dx, y0 + j*dy[, z0 + k*dz])
 * in IEEE 754 double precision — bit-identical to calling
 * simplex_noise{2,3}d() with the same coords from outside. The compiler
 * is expected to inline the noise function calls inside these loops at
 * -O3, eliminating per-sample call overhead.
 *
 * `out` is `restrict`: it must not alias the SimplexCtx — it never does
 * in normal use (output buffer vs read-only ctx).
 *
 * No-op (returns immediately) if either pointer is NULL.
 */

NSN_API NSN_HOT
void simplex_fill2d_grid(const SimplexCtx* ctx,
                          double x0, double dx, size_t w,
                          double y0, double dy, size_t h,
                          double * restrict out)
{
    if (!ctx || !out) return;
    for (size_t j = 0; j < h; j++) {
        double y = y0 + (double) j * dy;
        double * restrict row = out + j * w;
        for (size_t i = 0; i < w; i++) {
            double x = x0 + (double) i * dx;
            row[i] = simplex_noise2d(ctx, x, y);
        }
    }
}

NSN_API NSN_HOT
void simplex_fill3d_grid(const SimplexCtx* ctx,
                          double x0, double dx, size_t w,
                          double y0, double dy, size_t h,
                          double z0, double dz, size_t d,
                          double * restrict out)
{
    if (!ctx || !out) return;
    for (size_t k = 0; k < d; k++) {
        double z = z0 + (double) k * dz;
        double * restrict slab = out + k * h * w;
        for (size_t j = 0; j < h; j++) {
            double y = y0 + (double) j * dy;
            double * restrict row = slab + j * w;
            for (size_t i = 0; i < w; i++) {
                double x = x0 + (double) i * dx;
                row[i] = simplex_noise3d(ctx, x, y, z);
            }
        }
    }
}
