const std = @import("std");
const gtk = @import("gtk");
const gobject = @import("gobject");

/// A lightweight wrapper around gobject.WeakRef to make it type-safe
/// to hold a single type of value.
pub fn WeakRef(comptime T: type) type {
    return struct {
        const Self = @This();

        ref: gobject.WeakRef = std.mem.zeroes(gobject.WeakRef),

        pub const empty: Self = .{};

        /// Set the weak reference to the given object. This will not
        /// increase the reference count of the object.
        pub fn set(self: *Self, v_: ?*T) void {
            if (v_) |v| {
                self.ref.set(v.as(gobject.Object));
            } else {
                self.ref.set(null);
            }
        }

        /// Release this weak reference.
        ///
        /// You MUST call this before the memory holding this struct is freed,
        /// which in practice means from the owner's `dispose`. The target keeps
        /// a pointer to this `GWeakRef` so that it can clear it when the target
        /// is finalized; if this memory is gone by then, the target walks into
        /// whatever now occupies it. That is an invalid read at best, and can
        /// hang: the target takes a lock inside each registered weak ref, and
        /// reused memory with the low bit set is a lock nothing will release.
        ///
        /// `set(null)` also unregisters and remains valid. This exists so the
        /// requirement has a name at the use site rather than looking like an
        /// ordinary assignment.
        pub fn deinit(self: *Self) void {
            self.ref.clear();
        }

        /// Get a strong reference to the object, or null if the object
        /// has been finalized. This increases the reference count by one.
        pub fn get(self: *Self) ?*T {
            // We can't use `as` because `as` guarantees conversion and
            // that can't be statically guaranteed.
            return gobject.ext.cast(T, self.ref.get() orelse return null);
        }
    };
}

test WeakRef {
    const testing = std.testing;

    var ref: WeakRef(gtk.TextBuffer) = .empty;
    const obj: *gtk.TextBuffer = .new(null);
    ref.set(obj);
    ref.get().?.unref(); // The "?" asserts non-null
    obj.unref();
    try testing.expect(ref.get() == null);
}
