# Brute force, independent of the formulas: dither a field, round to integers, apply the filter (circular,
# by FFT), compare with the filtered field without rounding. Flats sweep the level; ramps sweep slope.
import numpy as np
import filters as F

def lattice_field(A, B, N, size, C=0):
    y, x = np.mgrid[0:size, 0:size]
    return (((A * x + B * y + C) % N) + .5) / N

def ign(size, x0=60, y0=1100):
    y, x = np.mgrid[0:size, 0:size].astype(np.float32) + np.float32(.5); x += x0; y += y0
    fr = lambda v: (v - np.floor(v)).astype(np.float32)
    return fr(np.float32(52.9829189) * fr(x * np.float32(0.06711056) + y * np.float32(0.00583715))).astype(float)

def r2(size):
    y, x = np.mgrid[0:size, 0:size]
    return (x * 0.7548776662 + y * 0.5698402910) % 1

def white(size, seed=1):
    return np.random.default_rng(seed).random((size, size))

def kernel_fft(taps, size):
    K = np.zeros((size, size))
    for dx, dy, w in taps: K[dy % size, dx % size] += w
    return np.fft.rfft2(K)

def filt(img, FK):
    return np.fft.irfft2(np.fft.rfft2(img) * FK, s=img.shape)

def flat_rms(u, taps, levels=64):
    FK = kernel_fft(taps, u.shape[0]); e2 = 0
    for f in (np.arange(levels) + .5) / levels:
        e2 += (filt(np.floor(f + u) - f, FK) ** 2).mean()
    return np.sqrt(e2 / levels)

def ramp_rms(u, taps, slope, angle_deg, offsets=16):
    """A linear ramp of `slope` levels per pixel. On a periodic canvas the ramp must wrap: use slopes whose
    total rise across the canvas is a whole number of levels (rounded here), so floor(v + u) - v is periodic."""
    n = u.shape[0]; FK = kernel_fft(taps, n)
    th = np.deg2rad(angle_deg)
    gx, gy = np.round(slope * np.cos(th) * n) / n, np.round(slope * np.sin(th) * n) / n
    y, x = np.mgrid[0:n, 0:n]; e2 = 0
    for o in (np.arange(offsets) + .5) / offsets:
        v = gx * x + gy * y + o
        e2 += (filt(np.floor(v + u) - v, FK) ** 2).mean()
    return np.sqrt(e2 / offsets)
