const std = @import("std");

const mpsc = @import("../mpsc.zig");
const queue = @import("queue.zig");

pub fn MsgQueueUnmanaged(comptime T: type) type {
    return struct {
        const Self = @This();

        queue: queue.Queue(T),
        // consumer_mutex: std.Thread.Mutex = std.Thread.Mutex{},
        cond: std.Thread.Condition = std.Thread.Condition{},

        pub fn init(buffer: []mpsc.Slot(T)) !Self {
            return .{
                .queue = try queue.Queue(T).init(buffer),
            };
        }

        pub fn capacity(self: *Self) usize {
            return self.queue.capacity();
        }

        pub fn tryEnqueue(self: *Self, value: T) !void {
            try self.queue.tryEnqueue(value);
            self.cond.broadcast();
        }

        pub fn enqueue(self: *Self, value: T) void {
            self.queue.enqueue(value);
            self.cond.broadcast();
        }

        pub fn tryDequeue(self: *Self) !T {
            return try self.queue.tryDequeue();
        }

        pub fn dequeue(self: *Self) T {
            var m = std.Thread.Mutex{};
            m.lock();
            defer m.unlock();

            while (true) {
                if (self.queue.dequeue()) |val| return val;
                self.cond.wait(&m);
            }
        }

        pub fn tryDequeueAll(self: *Self, buf: []T) ![]T {
            return try self.queue.tryDequeueAll(buf);
        }

        pub fn dequeueAll(self: *Self, buf: []T) ![]T {
            var m = std.Thread.Mutex{};
            m.lock();
            defer m.unlock();

            while (true) {
                const slice = try self.queue.tryDequeueAll(buf);
                if (slice.len > 0) return slice;
                self.cond.wait(&m);
            }
        }
    };
}

pub fn MsgQueueManaged(comptime T: type) type {
    return struct {
        const Self = @This();

        allocator: std.mem.Allocator,
        buffer: []mpsc.Slot(T),
        msg_queue: MsgQueueUnmanaged(T),

        pub fn init(allocator: std.mem.Allocator, cap: usize) !Self {
            const buffer = try allocator.alloc(mpsc.Slot(T), cap);
            errdefer allocator.free(buffer);

            return .{
                .allocator = allocator,
                .buffer = buffer,
                .msg_queue = try MsgQueueUnmanaged(T).init(buffer),
            };
        }

        pub fn deinit(self: *Self) void {
            self.allocator.free(self.buffer);
        }

        pub fn unmanaged(self: *Self) *MsgQueueUnmanaged(T) {
            return &self.msg_queue;
        }

        pub fn capacity(self: *Self) usize {
            return self.msg_queue.capacity();
        }

        pub fn tryEnqueue(self: *Self, value: T) !void {
            try self.msg_queue.tryEnqueue(value);
        }

        pub fn enqueue(self: *Self, value: T) void {
            self.msg_queue.enqueue(value);
        }

        pub fn tryDequeue(self: *Self) !T {
            return try self.msg_queue.tryDequeue();
        }

        pub fn dequeue(self: *Self) T {
            return self.msg_queue.dequeue();
        }

        pub fn tryDequeueAll(self: *Self, buf: []T) ![]T {
            return try self.msg_queue.tryDequeueAll(buf);
        }
    };
}
