// NTT (Number Theoretic Transform) implementation for BigDecimal multiplication

#define NTT_PRIMITIVE_ROOT 17
#define NTT_PRIME_BASE1 24
#define NTT_PRIME_BASE2 26
#define NTT_PRIME_BASE3 29
#define NTT_PRIME_SHIFT 27
#define NTT_PRIME1 (((uint32_t)NTT_PRIME_BASE1 << NTT_PRIME_SHIFT) | 1)
#define NTT_PRIME2 (((uint32_t)NTT_PRIME_BASE2 << NTT_PRIME_SHIFT) | 1)
#define NTT_PRIME3 (((uint32_t)NTT_PRIME_BASE3 << NTT_PRIME_SHIFT) | 1)
#define MAX_NTT32_BITS 27
#define NTT_DECDIG_BASE 1000000000

// Calculates base**ex % mod
static uint32_t
mod_pow(uint32_t base, uint32_t ex, uint32_t mod) {
    uint32_t res = 1;
    uint32_t bit = 1;
    while (true) {
        if (ex & bit) {
            ex ^= bit;
            res = ((uint64_t)res * base) % mod;
        }
        if (!ex) break;
        base = ((uint64_t)base * base) % mod;
        bit <<= 1;
    }
    return res;
}

// Recursively performs butterfly operations of NTT
static void
ntt_recursive(int size_bits, uint32_t *input, uint32_t *output, uint32_t *tmp, int depth, uint32_t r, uint32_t prime) {
    if (depth > 0) {
        ntt_recursive(size_bits, input, tmp, output, depth - 1, ((uint64_t)r * r) % prime, prime);
    } else {
        tmp = input;
    }
    uint32_t size_half = (uint32_t)1 << (size_bits - 1);
    uint32_t stride = (uint32_t)1 << (size_bits - depth - 1);
    uint32_t n = size_half / stride;
    uint32_t rn = 1, rm = prime - 1;
    for (uint32_t i = 0; i < n; i++) {
        uint32_t *aptr = tmp + i * 2 * stride;
        uint32_t *bptr = aptr + stride;
        uint32_t *out1 = output + stride * i;
        uint32_t *out2 = out1 + size_half;
        for (uint32_t k = 0; k < stride; k++) {
            uint32_t a = aptr[k], b = bptr[k];
            out1[k] = (a + (uint64_t)rn * b) % prime;
            out2[k] = (a + (uint64_t)rm * b) % prime;
        }
        rn = ((uint64_t)rn * r) % prime;
        rm = ((uint64_t)rm * r) % prime;
    }
}

/* Perform NTT on input array.
 * base, shift: Represent the prime number as (base << shift | 1)
 * r_base: Primitive root of unity modulo prime
 * size_bits: log2 of the size of the input array. Should be less or equal to shift
 * input: input array of size (1 << size_bits)
 */
static void
ntt(int size_bits, uint32_t *input, uint32_t *output, uint32_t *tmp, int r_base, int base, int shift, int dir) {
    uint32_t size = (uint32_t)1 << size_bits;
    uint32_t prime = ((uint32_t)base << shift) | 1;

    // rmax**(1 << shift) % prime == 1
    // r**size % prime == 1
    uint32_t rmax = mod_pow((uint32_t)r_base, (uint32_t)base, prime);
    uint32_t r = mod_pow(rmax, (uint32_t)1 << (shift - size_bits), prime);

    if (dir < 0) r = mod_pow(r, prime - 2, prime);
    ntt_recursive(size_bits, input, output, tmp, size_bits - 1, r, prime);
    if (dir < 0) {
        uint32_t n_inv = mod_pow((uint32_t)size, prime - 2, prime);
        for (uint32_t i = 0; i < size; i++) {
            output[i] = ((uint64_t)output[i] * n_inv) % prime;
        }
    }
}

/* Calculate c that satisfies: c % PRIME1 == mod1 && c % PRIME2 == mod2 && c % PRIME3 == mod3
 * c = (mod1 * 35002755423056150739595925972 + mod2 * 14584479687667766215746868453 + mod3 * 37919651490985126265126719818) % (PRIME1 * PRIME2 * PRIME3)
 * Assume c <= 999999999**2*(1<<27)
 */
static inline void
mod_restore_prime_24_26_29_shift_27(uint32_t mod1, uint32_t mod2, uint32_t mod3, uint32_t *digits) {
    // Use mixed radix notation to eliminate modulo by PRIME1 * PRIME2 * PRIME3
    // [DIG0, DIG1, DIG2] = DIG0 + DIG1 * PRIME1 + DIG2 * PRIME1 * PRIME2
    // DIG0: 0...PRIME1, DIG1: 0...PRIME2, DIG2: 0...PRIME3
    // 35002755423056150739595925972 = [1, 3489660916, 3113851359]
    // 14584479687667766215746868453 = [0, 13, 1297437912]
    // 37919651490985126265126719818 = [0, 0, 3373338954]
    uint64_t c0 = mod1;
    uint64_t c1 = (uint64_t)mod2 * 13 + (uint64_t)mod1 * 3489660916;
    uint64_t c2 = (uint64_t)mod3 * 3373338954 % NTT_PRIME3 + (uint64_t)mod2 * 1297437912 % NTT_PRIME3 + (uint64_t)mod1 * 3113851359 % NTT_PRIME3;
    c2 += c1 / NTT_PRIME2;
    c1 %= NTT_PRIME2;
    c2 %= NTT_PRIME3;
    // Base conversion. c fits in 3 digits.
    c1 += c2 % NTT_DECDIG_BASE * NTT_PRIME2;
    c0 += c1 % NTT_DECDIG_BASE * NTT_PRIME1;
    c1 /= NTT_DECDIG_BASE;
    digits[0] = c0 % NTT_DECDIG_BASE;
    c0 /= NTT_DECDIG_BASE;
    c1 += c2 / NTT_DECDIG_BASE % NTT_DECDIG_BASE * NTT_PRIME2;
    c0 += c1 % NTT_DECDIG_BASE * NTT_PRIME1;
    c1 /= NTT_DECDIG_BASE;
    digits[1] = c0 % NTT_DECDIG_BASE;
    digits[2] = (uint32_t)(c0 / NTT_DECDIG_BASE + c1 % NTT_DECDIG_BASE * NTT_PRIME1);
}

/*
 * NTT multiplication
 * Uses three NTTs with mod (24 << 27 | 1), (26 << 27 | 1), and (29 << 27 | 1)
 */
static void
ntt_multiply(size_t a_size, size_t b_size, uint32_t *a, uint32_t *b, uint32_t *c) {
    if (a_size < b_size) {
      ntt_multiply(b_size, a_size, b, a, c);
      return;
    }

    int ntt_size_bits = (int)bit_length(b_size - 1) + 1;
    if (ntt_size_bits > MAX_NTT32_BITS) {
      rb_raise(rb_eArgError, "Multiply size too large");
    }

    // To calculate large_a * small_b faster, split into several batches.
    uint32_t ntt_size = (uint32_t)1 << ntt_size_bits;
    uint32_t batch_size = ntt_size - (uint32_t)b_size;
    uint32_t batch_count = (uint32_t)((a_size + batch_size - 1) / batch_size);

    uint32_t *mem = ruby_xcalloc(ntt_size * 9, sizeof(uint32_t));
    uint32_t *ntt1 = mem;
    uint32_t *ntt2 = mem + ntt_size;
    uint32_t *ntt3 = mem + ntt_size * 2;
    uint32_t *tmp1 = mem + ntt_size * 3;
    uint32_t *tmp2 = mem + ntt_size * 4;
    uint32_t *tmp3 = mem + ntt_size * 5;
    uint32_t *conv1 = mem + ntt_size * 6;
    uint32_t *conv2 = mem + ntt_size * 7;
    uint32_t *conv3 = mem + ntt_size * 8;

    // Calculate NTT for b in three primes. Result is reused for each batch of a.
    memcpy(tmp1, b, b_size * sizeof(uint32_t));
    memset(tmp1 + b_size, 0, (ntt_size - b_size) * sizeof(uint32_t));
    ntt(ntt_size_bits, tmp1, ntt1, tmp2, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE1, NTT_PRIME_SHIFT, +1);
    ntt(ntt_size_bits, tmp1, ntt2, tmp2, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE2, NTT_PRIME_SHIFT, +1);
    ntt(ntt_size_bits, tmp1, ntt3, tmp2, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE3, NTT_PRIME_SHIFT, +1);

    memset(c, 0, (a_size + b_size) * sizeof(uint32_t));
    for (uint32_t idx = 0; idx < batch_count; idx++) {
        uint32_t len = idx == batch_count - 1 ? (uint32_t)a_size - idx * batch_size : batch_size;
        memcpy(tmp1, a + idx * batch_size, len * sizeof(uint32_t));
        memset(tmp1 + len, 0, (ntt_size - len) * sizeof(uint32_t));
        // Calculate convolution for this batch in three primes
        ntt(ntt_size_bits, tmp1, tmp2, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE1, NTT_PRIME_SHIFT, +1);
        for (uint32_t i = 0; i < ntt_size; i++) tmp2[i] = ((uint64_t)tmp2[i] * ntt1[i]) % NTT_PRIME1;
        ntt(ntt_size_bits, tmp2, conv1, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE1, NTT_PRIME_SHIFT, -1);
        ntt(ntt_size_bits, tmp1, tmp2, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE2, NTT_PRIME_SHIFT, +1);
        for (uint32_t i = 0; i < ntt_size; i++) tmp2[i] = ((uint64_t)tmp2[i] * ntt2[i]) % NTT_PRIME2;
        ntt(ntt_size_bits, tmp2, conv2, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE2, NTT_PRIME_SHIFT, -1);
        ntt(ntt_size_bits, tmp1, tmp2, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE3, NTT_PRIME_SHIFT, +1);
        for (uint32_t i = 0; i < ntt_size; i++) tmp2[i] = ((uint64_t)tmp2[i] * ntt3[i]) % NTT_PRIME3;
        ntt(ntt_size_bits, tmp2, conv3, tmp3, NTT_PRIMITIVE_ROOT, NTT_PRIME_BASE3, NTT_PRIME_SHIFT, -1);

        // Restore the original convolution value from three convolutions calculated in three primes.
        // Each convolution value is maximum 999999999**2*(1<<27)/2
        for (uint32_t i = 0; i < ntt_size; i++) {
            uint32_t dig[3];
            mod_restore_prime_24_26_29_shift_27(conv1[i], conv2[i], conv3[i], dig);
            // Maximum values of dig[0], dig[1], and dig[2] are 999999999, 999999999 and 67108863 respectively
            // Maximum overlapped sum (considering overlaps between 2 batches) is less than 4134217722
            // so this sum doesn't overflow uint32_t.
            for (int j = 0; j < 3; j++) {
                // Index check: if dig[j] is non-zero, assign index is within valid range.
                if (dig[j]) c[idx * batch_size + i + 1 - (uint32_t)j] += dig[j];
            }
        }
    }
    uint32_t carry = 0;
    for (int32_t i = (int32_t)(a_size + b_size - 1); i >= 0; i--) {
        uint32_t v = c[i] + carry;
        c[i] = v % NTT_DECDIG_BASE;
        carry = v / NTT_DECDIG_BASE;
    }
    ruby_xfree(mem);
}
