const std = @import("std");
const rl = @import("raylib");
const rg = @import("raygui");
const pr = @import("primitives.zig");
const plot = @import("plot.zig");

const allocator = std.heap.page_allocator;
var prng = std.rand.DefaultPrng.init(124346234556);
const rand = prng.random();

const screenWidth = 1200;
const screenHeight = 800;

fn randMinMaxFloat(min: f32, max: f32) f32 {
    var r1 = rand.float(f32);
    r1 = 2 * (r1 - 0.5); //-0.5..0.5
    return (@abs(min) + @abs(max)) / 2 * r1;
}

const Particle = struct {
    const Self = @This();
    position: pr.Vec = .{ .a = .{ .x = 0, .y = 0, .z = 0 } },
    velocity: pr.Vec = .{ .a = .{ .x = 0, .y = 0, .z = 0 } },
    acceleration: pr.Vec = .{ .a = .{ .x = 0, .y = 0, .z = 0 } },
    lifespan: u8 = 0,
    show: u8 = 0,
    size: f32 = 0,
    pub fn new(self: Self) void {
        _ = self;
    }
    pub fn update(self: *Self) void {
        self.velocity.add(self.acceleration);
        self.position.add(self.velocity);
        if (self.position.a.y > screenHeight) {
            self.velocity.a.y *= -0.9;
            // self.acceleration.a.y *= -0.5;
        }

        if (self.lifespan > 0) {
            self.lifespan -= 1;
        } else {
            self.show = 0;
        }
        self.acceleration.a.x = 0;
        self.acceleration.a.y = 0;
        self.acceleration.a.z = 0;
    }
    pub fn spawn(self: *Self, xaccel: f32, yaccel: f32) void {
        const xrnr = randMinMaxFloat(-xaccel, xaccel);
        const yrnr = randMinMaxFloat(-yaccel, yaccel);

        self.size = randMinMaxFloat(-2,2);
        self.lifespan = rand.intRangeAtMost(u8, 75, 255);
        self.acceleration.a.x = xrnr;
        self.acceleration.a.y = yrnr;
        self.position.a.x = @floatFromInt(rl.getMouseX() - screenWidth / 2);
        self.position.a.y = @floatFromInt(rl.getMouseY() - screenHeight / 2);
        self.velocity.a.x = 0;
        self.velocity.a.y = 0;
        self.position.a.color.r = rand.intRangeAtMost(u8, 0, 30);
        self.position.a.color.g = rand.intRangeAtMost(u8, 0, 255);
        self.position.a.color.b = rand.intRangeAtMost(u8, 0, 255);
        self.show = 1;
    }
    pub fn applyGravity(self: *Self, val: f32) void {
        self.velocity.a.y += val;
    }
    pub fn applyForce(self: *Self, vec: pr.Vec) void {
        self.acceleration.a.x += vec.a.x * 0.8;
        self.acceleration.a.y += vec.a.y * 0.8;
        self.acceleration.a.z = vec.a.z;
        self.velocity.a.x *= 0.995;
        self.velocity.a.y *= 0.995;
    }
};

const Emitter = struct {
    particleCount: u32 = 0,
    particles: []Particle = undefined,

    pub fn init(self: *Emitter) !void {
        var xrnr = randMinMaxFloat(-1, 1);
        var yrnr = randMinMaxFloat(-0.5, 0.5);
        self.particles = try allocator.alloc(Particle, 10000);
        for (self.particles) |*p| {
            xrnr = randMinMaxFloat(0, 2);
            yrnr = randMinMaxFloat(0, 2);
            p.* = Particle{ .velocity = .{ .a = .{ .x = 0, .y = 0 } }, .acceleration = .{ .a = .{ .x = xrnr, .y = yrnr } } };
        }
    }
    pub fn emit(self: *Emitter, count: u32, xaccel: f32, yaccel: f32) void {
        var i: u32 = 0;
        for (self.particles) |*p| {
            if (p.show == 0) {
                i += 1;
                p.spawn(xaccel, yaccel);
            }
            if (i >= count) {
                break;
            }
        }
    }
    pub fn update(self: *Emitter,att : []Attractor, texture: rl.Texture2D) void {
        var posx: i32 = 0;
        var posy: i32 = 0;

        var vec2 : rl.Vector2=undefined;

        if (rl.isMouseButtonPressed(rl.MouseButton.mouse_button_right)) {
            std.debug.print("right pressed\n",.{});

            for (att) |*at|
            {
                std.debug.print("loop\n",.{});
                if(at.init_done != 1)
                {
                    at.init(.{.a=.{.x=@floatFromInt(rl.getMouseX() - screenWidth / 2),.y=@floatFromInt(rl.getMouseY() - screenHeight / 2),.z=0}});
        
                    std.debug.print("attractor allocated\n",.{});
                    break;
                }
            }
        }

        for (att) |at|
        {
            at.draw();
        }
        for (self.particles) |*p| {
            if (p.show == 1) {
                p.applyGravity(0);
                for (att) |at|
                {
                    p.applyForce(at.attract(p.*));
                }
                p.update();

                posx = @intFromFloat(screenWidth / 2 + p.position.a.x);
                posy = @intFromFloat(screenHeight / 2 + p.position.a.y);
                //rl.drawRectangle(posx,posy,2,2,rl.Color{.r=p.position.a.color.r,.g=p.position.a.color.g,.b=p.position.a.color.b,.a=255});
                vec2 = .{.x = (screenWidth/2+p.position.a.x),.y = (screenHeight/2+p.position.a.y)};
                rl.drawTextureEx(texture, vec2,0.5,p.size, rl.Color.green);
            }
        }
    }
};
const Attractor = struct {
    vec: pr.Vec,
    init_done : u8 = 0,
    pub fn init(self : *Attractor, vec : pr.Vec) void
    {
        self.vec = vec;
        self.init_done = 1;
    }
    pub fn attract(self: Attractor, particle: Particle) pr.Vec {
        const distance = self.vec.sub(particle.position);
        var force: pr.Vec = undefined;
        if (distance.a.x != 0 and distance.a.y != 0) {
            force.a.x = distance.a.x / 200;
            force.a.y = distance.a.y / 200;
            force.a.z = 0;
        }
        return force;
    }
    pub fn draw(self: Attractor) void {
        const posx: i32 = @intFromFloat(screenWidth / 2 + self.vec.a.x);
        const posy: i32 = @intFromFloat(screenHeight / 2 + self.vec.a.y);
        rl.drawRectangle(posx, posy, 10, 10, rl.Color{ .r = 10, .g = 10, .b = 120, .a = 255 });
    }
};
fn ItoF(x: i32) f32 {
    return @as(f32, @floatFromInt(x));
}
fn rectangle(x: u16, y: u16, width: u16, height: u16) rl.Rectangle {
    return rl.Rectangle{ .x = @as(f32, @floatFromInt(x)), .y = @as(f32, @floatFromInt(y)), .width = @as(f32, @floatFromInt(width)), .height = @as(f32, @floatFromInt(height)) };
}

pub fn main() anyerror!void {
    const rect3 = rectangle(10, 150, 600, 10);
    const rect4 = rectangle(10, 170, 600, 10);
    const rect5 = rectangle(10, 190, 600, 10);

    var seed: u64 = undefined;
    try std.posix.getrandom(std.mem.asBytes(&seed));

    prng = std.rand.DefaultPrng.init(seed);

    var value: f32 = 0;
    var value2: f32 = 0;
    var value3: f32 = 0;

    var emitter = Emitter{};
    try emitter.init();

    //const img : rl.Texture = rl.loadTexture("img.png");
    rl.initWindow(screenWidth, screenHeight, "raylib-zig [core] example - basic window");
    defer rl.closeWindow(); // Close window and OpenGL context

    var texture: rl.Texture2D = undefined;
    const image: rl.Image = rl.loadImage("resources/blut.png");
    if (image.width > 0) {
        texture = rl.loadTextureFromImage(image);
    } else {
        std.debug.print("error opening resource", .{});
        std.posix.exit(1);
    }
    rl.unloadImage(image);

    rl.setTargetFPS(60); // Set our game to run at 60 frames-per-second

    const file = try plot.open("plot.dat");
    var plotStr : []u8 = undefined;
    var plotStrBuf : [128]u8 = undefined;
    try plot.log(file,"acceleration, position, velocity, show\n");
    var att: [10]Attractor = undefined;

    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(rl.Color{
            .r = 0,
            .g = 0,
            .b = 0,
            .a = 255,
        });

        _ = rg.guiSlider(rect3, "0", "500", &value, ItoF(0), ItoF(500));
        _ = rg.guiSlider(rect4, "-3", "3", &value2, ItoF(-100), ItoF(100));
        _ = rg.guiSlider(rect5, "-3", "3", &value3, -100, 100);

        if (rl.isMouseButtonDown(rl.MouseButton.mouse_button_left)) {
            emitter.emit(@as(u32, @intFromFloat(value)), value2, value3);
        }
        

        
        plotStr = try std.fmt.bufPrint(&plotStrBuf,"{d}, {d}, {d}, {d}\n",.{emitter.particles[0].acceleration.a.x,emitter.particles[0].position.a.x,emitter.particles[0].velocity.a.x,emitter.particles[0].show});
        try plot.log(file,plotStr);

        rl.beginBlendMode(rl.BlendMode.blend_additive);
        emitter.update(&att,texture);
        rl.endBlendMode();
    }
    plot.close(file);
}

test "simple test" {
    var list = std.ArrayList(i32).init(std.testing.allocator);
    defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
    try list.append(42);
    try std.testing.expectEqual(@as(i32, 42), list.pop());
}

test "random" {
    var seed: u64 = undefined;
    var r1: f32 = undefined;
    try std.posix.getrandom(std.mem.asBytes(&seed));

    prng = std.rand.DefaultPrng.init(seed);
    while (true) {
        r1 = randMinMaxFloat(-10, 10);
        //std.debug.print("randMinMaxFloat:{}\n",.{r1});
        if (r1 > 8 or r1 < -8) {
            std.debug.print("Greater:{}\n", .{r1});
        }
    }
}
