#include "Sketch.h"

struct Particle {
    vec2 position;
    vec2 velocity;
    Color c;
    float size;
};

struct ParticleGroup {
    std::vector<Particle> particles;
    float sizex;
    float sizey;
};

ParticleGroup group1;
ParticleGroup group2;
int frames;

void SketchPostInitialize() {}
void SketchPreRender() {}

ParticleGroup CreateParticleGroup(int n, const std::vector<Color>& colors) {
    ParticleGroup group;
    group.sizex = CanvasGetSize().x * Randomf(0.15f, 0.45f);
    group.sizey = CanvasGetSize().y * Randomf(0.15f, 0.45f);

    int start = colors.size() * Random01f();
    int end = Random(start, start + (colors.size() - start) * Random01f());
    bool constant_size = Random01f() < 0.3f;
    float default_size = (float)Random(1, 6);

    for (int i = 0; i < n; ++i) {
        group.particles.push_back({
            {
                Randomf(
                    (CanvasGetSize().x - group.sizex) * 0.5f,
                    (CanvasGetSize().x + group.sizex) * 0.5f),
                Randomf(
                    (CanvasGetSize().y - group.sizey) * 0.5f,
                    (CanvasGetSize().y + group.sizey) * 0.5f)
            },
            {
                0.0f, 0.0f
            },
            colors[Random(start, end)],
            constant_size ? default_size : (float)Random(1, 6)
        });
    }

    return group;
}

void GenerateParticles() {
    auto colors = ColorPaletteGet(EColorPalette::ColorPalette06);
    group1 = CreateParticleGroup(Random(100, 400), colors);
    group2 = CreateParticleGroup(Random(100, 400), colors);

    frames = 60 * Random(1, 3);
}

void SketchInitialize() {
    CanvasSetName("generative001");
    CanvasSetSize(680, 500);
    CanvasSetBackgroundColor(ColorParseHex("#F6F2EA"));
    CanvasSetClear(false);
    GenerateParticles();
}

void Rule(ParticleGroup& g0, ParticleGroup& g1, float g) {
    bool constant_speed = Random01f() < 0.5f;
    float default_speed = Random01f() * 0.4f + 0.4f;
    for (int i = 0; i < g0.particles.size(); ++i) {
        auto& p0 = g0.particles[i];
        vec2 f(0.0f);

        for (int j = 0; j < g1.particles.size(); ++j) {
            auto& p1 = g1.particles[j];
            float dx = p0.position.x - p1.position.x;
            float dy = p0.position.y - p1.position.y;
            float d = distance(p1.position, p0.position);
            if (d > 0.0f && d < 80.0f) {
                float F = g * (1.0f / d);
                f.x += (F * dx);
                f.y += (F * dy);
            }
        }
        float speed = constant_speed ? default_speed : (Random01f() * 0.4f + 0.4f);

        p0.velocity = (p0.velocity + f) * speed;
        p0.position += p0.velocity;

        if (p0.position.x <= (CanvasGetSize().x - g0.sizex) * 0.5f ||
            p0.position.x >= (CanvasGetSize().x + g0.sizex) * 0.5f) {
            p0.velocity.x *= -4.0f;
        }

        if (p0.position.y <= (CanvasGetSize().y - g0.sizey) * 0.5f ||
            p0.position.y >= (CanvasGetSize().y + g0.sizey) * 0.5f) {
            p0.velocity.y *= -4.0f;
        }
    }
}

void SketchRender() {
    static int counter = 0;

    if (CanvasGetClear()) {
        CanvasSetClear(false);
    }
    if (KeyboardIsKeyReleased(SAPP_KEYCODE_G)) {
        GenerateParticles();
        counter = 0;
        CanvasSetClear(true);
    }

    if (++counter > frames) {
        return;
    }

    Rule(group2, group1, -0.1f);
    Rule(group2, group1, -0.01f);
    Rule(group1, group2, 0.01f);

    for (int i = 0; i < group1.particles.size(); ++i) {
        Particle& p = group1.particles[i];
        float n = NoisePerlin(
            vec2(MathMapRange01(p.position.x, 0, CanvasGetSize().x),
                 MathMapRange01(p.position.y, 0, CanvasGetSize().y)) * 10.5f);

        float s = MathMapRange01(n, -1.0f, 1.0f);
        float a = 0.15f * s;

        Canvas2DCircle({p.position.x, p.position.y}, p.size, ColorSetAlpha(p.c, a));
    }

    for (int i = 0; i < group2.particles.size(); ++i) {
        Particle& p = group2.particles[i];
        float n = NoisePerlin(
            vec2(MathMapRange01(p.position.x, 0, CanvasGetSize().x),
                 MathMapRange01(p.position.y, 0, CanvasGetSize().y)) * 10.5f);

        float s = MathMapRange01(n, -1.0f, 1.0f);
        float a = 0.15f * s;

        Canvas2DCircle({p.position.x, p.position.y}, p.size, ColorSetAlpha(p.c, a));
    }
}
