#+vet explicit-allocators
#+private file
package back

@require import "base:runtime"
@require import "base:intrinsics"

@require import "core:c"
@require import "core:c/libc"
@require import "core:os"
@require import "core:strings"

when !USE_FALLBACK {

foreign import lib "system:c"

@(private="package")
_Trace_Entry :: rawptr

@(private="package")
_trace :: #force_no_inline proc(buf: Trace) -> (n: int) {
	// In order to omit this function's frame and the caller, we alloca a temp buffer with 2 extra slots.
	bigger_buf := ([^]Trace_Entry)(intrinsics.alloca((2 + len(buf)) * size_of(Trace_Entry), align_of(Trace_Entry)))[:len(buf)+2]
	_n         := int(backtrace(raw_data(bigger_buf), i32(len(bigger_buf))))
	if _n > 2 {
		copy(buf, bigger_buf[2:])
		return _n-2
	}

	return 0
}

@(private="package")
_lines_destroy :: proc(msgs: []Line, allocator: runtime.Allocator) {
	for msg in msgs {
		delete(msg.location, allocator)
		if msg.symbol != "??OOM" && msg.symbol != "??" { delete(msg.symbol, allocator) }
	}
	delete(msgs, allocator)
}

@(private="package")
_lines :: proc(bt: Trace, allocator, temp_allocator: runtime.Allocator) -> (out: []Line, err: Lines_Error) {
	msgs := backtrace_symbols(raw_data(bt), i32(len(bt)))[:len(bt)]
	defer libc.free(raw_data(msgs))

	out = make([]Line, len(bt), allocator)
	defer if err != nil { _lines_destroy(out, allocator) }

	// Debug info is needed.
	when !ODIN_DEBUG {
		for msg, i in msgs {
			location, mem_err := strings.clone_from(msg, allocator)
			if mem_err != nil { return out, .Out_Of_Memory }

			out[i] = Line {
				location = location,
				symbol   = "??",
			}
		}
		return
	}

	i := 0

	command := make([dynamic]string, temp_allocator)
	defer delete(command)

	if _, err := append(&command, ADDR2LINE_PATH, "--functions", "--exe", ""); err != nil { return out, .Out_Of_Memory }

	COMMAND_EXE_POS   :: 3
	COMMAND_START_LEN :: 4

	for msg in msgs {
		exe, addr := parse_address(msg) or_return
		if command[COMMAND_EXE_POS] == "" {
			command[COMMAND_EXE_POS] = exe
		} else if command[COMMAND_EXE_POS] != exe {
			i += exec_and_fill(command[:], out[i:], msgs[i:], allocator, temp_allocator) or_return

			command[COMMAND_EXE_POS] = exe
			resize(&command, COMMAND_START_LEN)
		}

		if _, err := append(&command, addr); err != nil { return out, .Out_Of_Memory }
	}

	if len(command) > COMMAND_START_LEN {
		i += exec_and_fill(command[:], out[i:], msgs[i:], allocator, temp_allocator) or_return
	}

	return

	// Parses the exe and address out of a backtrace line.
	// Example: .../main(+0x20) [0x100000] -> .../main, +0x20, nil
	parse_address :: proc(cmsg: cstring) -> (string, string, Lines_Error) {
		msg := string(cmsg)
		close_idx := strings.last_index_byte(msg, ')')
		if close_idx < 1 {
			return "", "", .Parse_Address_Fail
		}

		open_idx := strings.last_index_byte(msg[:close_idx], '(')
		if open_idx < 0 {
			return "", "", .Parse_Address_Fail
		}

		return msg[:open_idx], msg[open_idx+1:close_idx], nil
	}

	exec_and_fill :: proc(command: []string, out: []Line, msgs: []cstring, allocator, temp_allocator: runtime.Allocator) -> (filled: int, err: Lines_Error) {
		state, stdout, stderr, perr := os.process_exec({command = command}, temp_allocator)
		defer delete(stdout, temp_allocator)
		defer delete(stderr, temp_allocator)

		if perr != nil || !state.success {
			return 0, .Addr2line_Process_Error
		}

		count := len(command)-COMMAND_START_LEN

		sstdout := string(stdout)
		for i in 0..<count {
			out[i].symbol, err = process_line(strings.split_lines_iterator(&sstdout), allocator)
			if err == .Out_Of_Memory {
				out[i].symbol = "??OOM"
			} else if err != nil {
				return
			}

			// TODO: parse location and transform to ODIN_ERROR_POS_STYLE
			out[i].location, err = process_line(strings.split_lines_iterator(&sstdout), allocator)
			if err == .Out_Of_Memory {
				out[i].location = "??OOM"
			} else if err != nil {
				return
			}

			if out[i].location == "" || out[i].location == "??" {
				fallback, mem_err := strings.clone_from(msgs[i], allocator)
				if mem_err != nil { return 0, .Out_Of_Memory }
				out[i].location = fallback
			}
		}

		return count, nil
	}

	process_line :: proc(line: string, ok: bool, allocator: runtime.Allocator) -> (string, Lines_Error) {
		if !ok { return "", .Addr2line_Unexpected_EOF }
		if line == "" { return "", .Addr2line_Output_Error }

		if line == "??" {
			return "??", nil
		}

		ret, err := strings.clone(strings.trim_right_space(line), allocator)
		return ret, err == nil ? nil : .Out_Of_Memory
	}

}

foreign lib {
	backtrace :: proc(buffer: [^]rawptr, size: c.int) -> c.int ---
	backtrace_symbols :: proc(buffer: [^]rawptr, size: c.int) -> [^]cstring ---
	backtrace_symbols_fd :: proc(buffer: [^]rawptr, size: c.int, fd: ^libc.FILE) ---
}

}
