// zig fmt: off
const std = @import("std");

const RateLimit = @This();

const assert = std.debug.assert;

pub const Category = enum { account_creation, token, data, actions };

pub const Timespan = struct {
    counter: u32 = 0,
    limit: u32,
    timer_ms: u64 = 0,
};

pub const CategoryArray = std.EnumArray(Category, RateLimit);

seconds: ?Timespan = null,
minutes: ?Timespan = null,
hours: ?Timespan = null,

last_update_at_ms: i64 = 0,

pub fn init(now_ms: i64, limit_per_hour: ?u32, limit_per_minute: ?u32, limit_per_second: ?u32) RateLimit {
    var seconds: ?Timespan = null;
    if (limit_per_second) |limit| {
        seconds = Timespan{ .limit = limit };
    }

    var minutes: ?Timespan = null;
    if (limit_per_minute) |limit| {
        minutes = Timespan{ .limit = limit };
    }

    var hours: ?Timespan = null;
    if (limit_per_hour) |limit| {
        hours = Timespan{ .limit = limit };
    }

    return RateLimit{
        .seconds = seconds,
        .minutes = minutes,
        .hours = hours,
        .last_update_at_ms = now_ms
    };
}

pub fn update_timers(self: *RateLimit, now_ms: i64) void {
    const time_passed_ms = now_ms - self.last_update_at_ms;
    assert(time_passed_ms >= 0);

    inline for (.{
        .{ &self.seconds, std.time.ms_per_s },
        .{ &self.minutes, std.time.ms_per_min },
        .{ &self.hours, std.time.ms_per_hour },
    }) |tuple| {
        const maybe_timespan = tuple[0];
        const timespan_size = tuple[1];

        if (maybe_timespan.*) |*timespan| {
            timespan.timer_ms += @intCast(time_passed_ms);

            const ms_per_request = @divFloor(timespan_size, timespan.limit);
            const requests_passed: u32 = @intCast(@divFloor(timespan.timer_ms, ms_per_request));
            timespan.counter -= @min(timespan.counter, requests_passed);
            timespan.timer_ms = @mod(timespan.timer_ms, ms_per_request);
        }
    }

    self.last_update_at_ms = now_ms;
}

pub fn increment_counters(self: *RateLimit) void {
    inline for (.{
        &self.hours,
        &self.minutes,
        &self.seconds,
    }) |maybe_timespan| {
        if (maybe_timespan.*) |*timespan| {
            timespan.counter += 1;
        }
    }
}
