const ff = @import("ff");
const math = @import("std").math;

const pal: ff.Palette = .{
    .black = 0x05070A, // almost black, slight blue tint
    .purple = 0x0A0F14, // repurposed as deep shadow
    .red = 0x0F1A14, // very dark green shadow
    .orange = 0x13261C, // dark green base
    .yellow = 0x1A3322, // low-mid green
    .light_green = 0x2A5A3A, // mid green
    .green = 0x3F8F4E, // brighter green
    .dark_green = 0x2F6B3C, // supporting mid-dark
    .dark_blue = 0x081018, // deep background blue
    .blue = 0x163A2C, // green-blue transition
    .light_blue = 0x2F7A5A, // teal-green
    .cyan = 0x4CCB88, // bright mint
    .white = 0xD6FFB3, // soft yellow highlight
    .light_gray = 0xA6D98A, // pale green-yellow
    .gray = 0x5C7F5A, // muted green-gray
    .dark_gray = 0x1C2A24, // dark desaturated green
};

pub export fn boot() void {
    pal.set();
    plasma.init();
}

pub export fn update() void {
    plasma.update();
}

pub export fn render() void {
    plasma.draw();
}

const s = 8;

const cx = ff.width / s;
const cy = ff.height / s;

const Plasma = struct {
    t: f32 = 0,

    xs: [cx]f32 = undefined,
    ys: [cy]f32 = undefined,
    xp: [cx]f32 = undefined,
    yp: [cy]f32 = undefined,
    x2: [cx]f32 = undefined,
    y2: [cy]f32 = undefined,

    fn init(self: *Plasma) void {
        self.t = 0;

        for (0..cx) |xi| {
            const fx = @as(f32, @floatFromInt(xi * s));
            self.xp[xi] = fx * 0.0625;
        }

        for (0..cy) |yi| {
            const fy = @as(f32, @floatFromInt(yi * s));
            self.yp[yi] = fy * 0.0625;
        }

        for (0..cy) |yi| {
            const fy = @as(f32, @floatFromInt(yi * s));
            for (0..cx) |xi| {
                const fx = @as(f32, @floatFromInt(xi * s));

                self.x2[xi] = fx * 0.03125;
                self.y2[yi] = fy * 0.03125;
            }
        }
    }

    fn update(self: *Plasma) void {
        self.t += 0.04;
    }

    fn draw(self: *Plasma) void {
        const t0 = self.t;
        const t1 = self.t * 1.3;
        const t2 = self.t * 0.7;

        for (0..cx) |xi| {
            self.xs[xi] = math.sin(self.xp[xi] + t0);
        }

        for (0..cy) |yi| {
            self.ys[yi] = math.sin(self.yp[yi] + t1);
        }

        for (0..cy) |yi| {
            for (0..cx) |xi| {
                const v =
                    self.xs[xi] +
                    self.ys[yi] +
                    math.sin(self.x2[xi] + self.y2[yi] + t2);

                ff.draw.rect(@intCast(xi * s), @intCast(yi * s), s, s, .{
                    .fill_color = colorFromValue(v, t0),
                });
            }
        }
    }
};

var plasma = Plasma{};

fn colorFromValue(v: f32, t: f32) ff.Color {
    const normalized = (v + 3.0) * (1.0 / 6.0);
    const shifted = normalized + t * 0.1;
    const wrapped = shifted - @floor(shifted);
    const idx: u8 = @intFromFloat(wrapped * 15);

    return paletteIndex(idx);
}

const palette_table = [_]ff.Color{
    .black,
    .dark_blue,
    .blue,
    .light_blue,
    .cyan,
    .light_green,
    .green,
    .dark_green,
    .yellow,
    .orange,
    .red,
    .purple,
    .dark_gray,
    .gray,
    .light_gray,
    .white,
};

inline fn paletteIndex(i: u8) ff.Color {
    return palette_table[i];
}
