package spall

import "core:os"
import "core:time"
import "core:intrinsics"
import "core:mem"

// File Format

MANUAL_MAGIC :: u64le(0x0BADF00D)

Manual_Header :: struct #packed {
	magic:           u64le,
	version:         u64le,
	timestamp_scale: f64le,
	reserved:        u64le,
}

Manual_Event_Type :: enum u8 {
	Invalid             = 0,

	Begin               = 3,
	End                 = 4,
	Instant             = 5,

	Pad_Skip            = 7,
}

Begin_Event :: struct #packed {
	type:     Manual_Event_Type,
	category: u8,
	pid:      u32le,
	tid:      u32le,
	ts:       f64le,
	name_len: u8,
	args_len: u8,
}
BEGIN_EVENT_MAX :: size_of(Begin_Event) + 255 + 255

End_Event :: struct #packed {
	type: Manual_Event_Type,
	pid:  u32le,
	tid:  u32le,
	ts:   f64le,
}

Pad_Skip :: struct #packed {
	type: Manual_Event_Type,
	size: u32le,
}

// User Interface

Context :: struct {
	precise_time:    bool,
	timestamp_scale: f64,
	fd:              os.Handle,
}

Buffer :: struct {
	data: []u8,
	head: int,
	tid:  u32,
	pid:  u32,
}

BUFFER_DEFAULT_SIZE :: 0x10_0000


context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok {
	fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600)
	if err != os.ERROR_NONE {
		return
	}

	ctx.fd = fd
	ctx.precise_time = precise_time
	ctx.timestamp_scale = timestamp_scale

	temp := [size_of(Manual_Header)]u8{}
	_build_header(temp[:], ctx.timestamp_scale)
	os.write(ctx.fd, temp[:])
	ok = true
	return
}

context_create_with_sleep :: proc(filename: string, sleep := 2 * time.Second) -> (ctx: Context, ok: bool) #optional_ok {
	freq, freq_ok := time.tsc_frequency(sleep)
	timestamp_scale: f64 = ((1 / f64(freq)) * 1_000_000) if freq_ok else 1
	return context_create_with_scale(filename, freq_ok, timestamp_scale)
}

context_create :: proc{context_create_with_scale, context_create_with_sleep}

context_destroy :: proc(ctx: ^Context) {
	if ctx == nil {
		return
	}

	os.close(ctx.fd)
	ctx^ = Context{}
}

buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
	assert(len(data) >= 1024)
	buffer.data = data
	buffer.tid  = tid
	buffer.pid  = pid
	buffer.head = 0
	ok = true
	return
}

buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
	start := _trace_now(ctx)
	os.write(ctx.fd, buffer.data[:buffer.head])
	buffer.head = 0
	end := _trace_now(ctx)

	buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start, buffer.tid, buffer.pid)
	buffer.head += _build_end(buffer.data[buffer.head:], end, buffer.tid, buffer.pid)
}

buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) {
	buffer_flush(ctx, buffer)

	buffer^ = Buffer{}
}



@(deferred_in=_scoped_buffer_end)
SCOPED_EVENT :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) -> bool {
	_buffer_begin(ctx, buffer, name, args, location)
	return true
}

@(private)
_scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #caller_location) {
	_buffer_end(ctx, buffer)
}


_trace_now :: proc "contextless" (ctx: ^Context) -> f64 {
	if !ctx.precise_time {
		return f64(time.tick_now()._nsec) / 1_000
	}

	return f64(intrinsics.read_cycle_counter())
}

_build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok {
	header_size = size_of(Manual_Header)
	if header_size > len(buffer) {
		return 0, false
	}

	hdr := (^Manual_Header)(raw_data(buffer))
	hdr.magic = MANUAL_MAGIC
	hdr.version = 1
	hdr.timestamp_scale = f64le(timestamp_scale)
	hdr.reserved = 0
	ok = true
	return
}

_build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
	ev := (^Begin_Event)(raw_data(buffer))
	name_len := min(len(name), 255)
	args_len := min(len(args), 255)

	event_size = size_of(Begin_Event) + name_len + args_len
	if event_size > len(buffer) {
		return 0, false
	}

	ev.type = .Begin
	ev.pid  = u32le(pid)
	ev.tid  = u32le(tid)
	ev.ts   = f64le(ts)
	ev.name_len = u8(name_len)
	ev.args_len = u8(args_len)
	mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
	mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
	ok = true

	return
}

_build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
	ev := (^End_Event)(raw_data(buffer))
	event_size = size_of(End_Event)
	if event_size > len(buffer) {
		return 0, false
	}

	ev.type = .End
	ev.pid  = u32le(pid)
	ev.tid  = u32le(tid)
	ev.ts   = f64le(ts)
	ok = true

	return
}

_buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) {
	if buffer.head + BEGIN_EVENT_MAX > len(buffer.data) {
		buffer_flush(ctx, buffer)
	}
	name := location.procedure if name == "" else name
	buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid)
}

_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) {
	ts := _trace_now(ctx)

	if buffer.head + size_of(End_Event) > len(buffer.data) {
		buffer_flush(ctx, buffer)
	}

	buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid)
}
