//! Tracy API.
//!
//! Forked and modified from https://github.com/SpexGuy/Zig-Tracy
const std = @import("std");
const builtin = @import("builtin");
const root = @import("root");
const SourceLocation = std.builtin.SourceLocation;

// Tracy is enabled if the root function tracy_enabled returns true.
pub const enabled = @hasDecl(root, "tracy_enabled") and root.tracy_enabled();

// Bring in the correct implementation depending on if we're enabled or not.
// See Impl for all the real doc comments.
pub usingnamespace if (enabled) Impl else Noop;

const Impl = struct {
    const c = @cImport({
        //uncomment to enable callstacks, very slow
        //@cDefine("TRACY_CALLSTACK", "");

        @cDefine("TRACY_ENABLE", "");
        @cInclude("TracyC.h");
    });

    const has_callstack_support = @hasDecl(c, "TRACY_HAS_CALLSTACK") and @hasDecl(c, "TRACY_CALLSTACK");
    const callstack_enabled: c_int = if (has_callstack_support) c.TRACY_CALLSTACK else 0;
    const callstack_depth = 10; // TODO configurable

    /// A zone represents the lifetime of a special on-stack profiler variable.
    /// Typically it would exist for the duration of a whole scope of the
    /// profiled function, but you also can measure time spent in scopes of a
    /// for-loop or an if-branch.
    pub const Zone = struct {
        zone: c.___tracy_c_zone_context,

        /// Text description of a zone.
        pub inline fn text(self: Zone, val: []const u8) void {
            c.___tracy_emit_zone_text(self.zone, val.ptr, val.len);
        }

        /// Name of the zone.
        pub inline fn name(self: Zone, val: []const u8) void {
            c.___tracy_emit_zone_name(self.zone, val.ptr, val.len);
        }

        /// Color of the zone in the UI. Specify the value as RGB
        /// using hex: 0xRRGGBB.
        pub inline fn color(self: Zone, val: u32) void {
            c.___tracy_emit_zone_color(self.zone, val);
        }

        /// A value associated with the zone.
        pub inline fn value(self: Zone, val: u64) void {
            c.___tracy_emit_zone_value(self.zone, val);
        }

        /// End the zone.
        pub inline fn end(self: Zone) void {
            c.___tracy_emit_zone_end(self.zone);
        }
    };

    /// Tracy profiles within the context of a frame. This represents
    /// a single frame.
    pub fn Frame(comptime name: [:0]const u8) type {
        return struct {
            pub fn end(_: @This()) void {
                c.___tracy_emit_frame_mark_end(name.ptr);
            }
        };
    }

    /// allocator returns an allocator that tracks allocs/frees.
    pub fn allocator(
        parent: std.mem.Allocator,
        comptime name: ?[:0]const u8,
    ) Allocator(name) {
        return Allocator(name).init(parent);
    }

    /// Returns an allocator type with the given name.
    pub fn Allocator(comptime name: ?[:0]const u8) type {
        return struct {
            parent: std.mem.Allocator,

            const Self = @This();

            pub fn init(parent: std.mem.Allocator) Self {
                return .{ .parent = parent };
            }

            pub fn allocator(self: *Self) std.mem.Allocator {
                return .{
                    .ptr = self,
                    .vtable = &.{
                        .alloc = allocFn,
                        .resize = resizeFn,
                        .free = freeFn,
                    },
                };
            }

            fn allocFn(
                ctx: *anyopaque,
                len: usize,
                log2_ptr_align: u8,
                ret_addr: usize,
            ) ?[*]u8 {
                const self = @as(*Self, @ptrCast(@alignCast(ctx)));
                const result = self.parent.rawAlloc(len, log2_ptr_align, ret_addr);
                if (result) |data| {
                    if (len != 0) {
                        if (name) |n| {
                            allocNamed(data.ptr, len, n);
                        } else {
                            alloc(data, len);
                        }
                    }
                }

                return result;
            }

            fn resizeFn(
                ctx: *anyopaque,
                buf: []u8,
                log2_buf_align: u8,
                new_len: usize,
                ret_addr: usize,
            ) bool {
                const self = @as(*Self, @ptrCast(@alignCast(ctx)));
                if (self.parent.rawResize(buf, log2_buf_align, new_len, ret_addr)) {
                    if (name) |n| {
                        freeNamed(buf.ptr, n);
                        allocNamed(buf.ptr, new_len, n);
                    } else {
                        free(buf.ptr);
                        alloc(buf.ptr, new_len);
                    }

                    return true;
                }

                // during normal operation the compiler hits this case thousands of times due to this
                // emitting messages for it is both slow and causes clutter
                return false;
            }

            fn freeFn(
                ctx: *anyopaque,
                buf: []u8,
                log2_buf_align: u8,
                ret_addr: usize,
            ) void {
                const self = @as(*Self, @ptrCast(@alignCast(ctx)));
                self.parent.rawFree(buf, log2_buf_align, ret_addr);

                if (buf.len != 0) {
                    if (name) |n| {
                        freeNamed(buf.ptr, n);
                    } else {
                        free(buf.ptr);
                    }
                }
            }
        };
    }

    /// Start a trace. Defer calling end() to end the trace.
    pub inline fn trace(comptime src: SourceLocation) Zone {
        const static = struct {
            var loc: c.___tracy_source_location_data = undefined;
        };
        static.loc = .{
            .name = null,
            .function = src.fn_name.ptr,
            .file = src.file.ptr,
            .line = src.line,
            .color = 0,
        };

        const zone = if (has_callstack_support)
            c.___tracy_emit_zone_begin_callstack(&static.loc, callstack_depth, 1)
        else
            c.___tracy_emit_zone_begin(&static.loc, 1);

        return Zone{ .zone = zone };
    }

    /// Mark the boundary of a frame. Good for continuous frames. For
    /// discontinuous frames, use frame() and defer end().
    pub inline fn frameMark() void {
        c.___tracy_emit_frame_mark(null);
    }

    /// Start a discontinuous frame.
    pub inline fn frame(comptime name: [:0]const u8) Frame(name) {
        c.___tracy_emit_frame_mark_start(name.ptr);
        return .{};
    }

    /// Name the current thread.
    pub inline fn setThreadName(comptime name: [:0]const u8) void {
        c.___tracy_set_thread_name(name.ptr);
    }

    inline fn alloc(ptr: [*]u8, len: usize) void {
        if (has_callstack_support) {
            c.___tracy_emit_memory_alloc_callstack(ptr, len, callstack_depth, 0);
        } else {
            c.___tracy_emit_memory_alloc(ptr, len, 0);
        }
    }

    inline fn allocNamed(ptr: [*]u8, len: usize, comptime name: [:0]const u8) void {
        if (has_callstack_support) {
            c.___tracy_emit_memory_alloc_callstack_named(ptr, len, callstack_depth, 0, name.ptr);
        } else {
            c.___tracy_emit_memory_alloc_named(ptr, len, 0, name.ptr);
        }
    }

    inline fn free(ptr: [*]u8) void {
        if (has_callstack_support) {
            c.___tracy_emit_memory_free_callstack(ptr, callstack_depth, 0);
        } else {
            c.___tracy_emit_memory_free(ptr, 0);
        }
    }

    inline fn freeNamed(ptr: [*]u8, comptime name: [:0]const u8) void {
        if (has_callstack_support) {
            c.___tracy_emit_memory_free_callstack_named(ptr, callstack_depth, 0, name.ptr);
        } else {
            c.___tracy_emit_memory_free_named(ptr, 0, name.ptr);
        }
    }
};

const Noop = struct {
    pub const Zone = struct {
        pub inline fn text(_: Zone, _: []const u8) void {}
        pub inline fn name(_: Zone, _: []const u8) void {}
        pub inline fn color(_: Zone, _: u32) void {}
        pub inline fn value(_: Zone, _: u64) void {}
        pub inline fn end(_: Zone) void {}
    };

    pub fn Frame(comptime _: [:0]const u8) type {
        return struct {
            pub fn end(_: @This()) void {}
        };
    }

    pub inline fn trace(comptime src: SourceLocation) Zone {
        _ = src;
        return .{};
    }

    pub inline fn frameMark() void {}

    pub inline fn frame(comptime name: [:0]const u8) Frame(name) {
        return .{};
    }

    pub inline fn setThreadName(comptime name: [:0]const u8) void {
        _ = name;
    }
};

test {}
