# --- settings ---

name := "clog"
std  := "c23"
cc   := "gcc"

# --- directories ---

src_dir   := "src"
inc_dir   := "include"
lib_dir   := "lib"
test_dir  := "tests"
build_dir := ".build"

# --- platform ---

so_ext   := if os() == "macos" { "dylib" } else { "so" }
so_flags := if os() == "macos" { "-dynamiclib" } else { "-shared" }

# --- cflags ---

warn := "-Wall -Wextra -Wfloat-equal -Wundef -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wconversion -Wunreachable-code -pedantic"
base := "-std=" + std + " " + warn + " -I" + inc_dir
san  := "-fsanitize=address,undefined"

# Static:  put .a files in lib/static/ and append  -L lib/static  -lfoo
# Dynamic: put .so files in lib/dynamic/ and append  -L lib/dynamic -lbar
lnk := "-L" + lib_dir + "/static -L" + lib_dir + "/dynamic -lm"

# --- build ---

# Build both static and dynamic libraries (default)
build: static dynamic

[group('build')]
static:
	#!/usr/bin/env bash
	set -euo pipefail
	mkdir -p {{build_dir}}
	objs=()
	for f in $(find {{src_dir}} -name '*.c'); do
		obj="{{build_dir}}/$(basename "$f" .c).o"
		echo {{cc}} {{base}} -O2 -DNDEBUG -fPIC -c "$f" -o "$obj"
		{{cc}} {{base}} -O2 -DNDEBUG -fPIC -c "$f" -o "$obj"
		objs+=("$obj")
	done
	ar rcs {{build_dir}}/lib{{name}}.a "${objs[@]}"
	echo "Built: {{build_dir}}/lib{{name}}.a"

[group('build')]
dynamic:
	#!/usr/bin/env bash
	set -euo pipefail
	mkdir -p {{build_dir}}
	objs=()
	for f in $(find {{src_dir}} -name '*.c'); do
		obj="{{build_dir}}/$(basename "$f" .c).o"
		{{cc}} {{base}} -O2 -DNDEBUG -fPIC -c "$f" -o "$obj"
		objs+=("$obj")
	done
	{{cc}} {{so_flags}} -o {{build_dir}}/lib{{name}}.{{so_ext}} "${objs[@]}" {{lnk}}
	echo "Built: {{build_dir}}/lib{{name}}.{{so_ext}}"

# --- checks ---

[group('check')]
test:
	#!/usr/bin/env bash
	set -euo pipefail
	mkdir -p {{build_dir}}/tests
	srcs=$(find {{src_dir}} -name '*.c' | tr '\n' ' ')
	failed=0
	for f in $(find {{test_dir}} -name 'test_*.c'); do
		tname=$(basename "$f" .c)
		printf '  %-30s' "$tname"
		{{cc}} {{base}} -I{{test_dir}} -g -O1 {{san}} "$f" $srcs {{lnk}} \
			-o {{build_dir}}/tests/"$tname"
		if {{build_dir}}/tests/"$tname"; then
			printf 'ok\n'
		else
			printf 'FAILED\n'
			failed=$((failed + 1))
		fi
	done
	[ $failed -eq 0 ] || { echo "$failed test(s) failed"; exit 1; }

# Run tests under Valgrind (Linux only)
[group('check')]
valgrind:
	#!/usr/bin/env bash
	set -euo pipefail
	mkdir -p {{build_dir}}/tests
	srcs=$(find {{src_dir}} -name '*.c' | tr '\n' ' ')
	for f in $(find {{test_dir}} -name 'test_*.c'); do
		tname=$(basename "$f" .c)
		echo "==> valgrind: $tname"
		{{cc}} {{base}} -I{{test_dir}} -g -O1 "$f" $srcs {{lnk}} \
			-o {{build_dir}}/tests/"$tname"-vg
		valgrind --leak-check=full --show-leak-kinds=all \
			--track-origins=yes --error-exitcode=1 \
			{{build_dir}}/tests/"$tname"-vg
	done

[group('check')]
cppcheck:
	cppcheck --enable=all --std={{std}} --inconclusive --error-exitcode=1 \
		-I {{inc_dir}} {{src_dir}}/

# --- formatting ---

[group('fmt')]
fmt:
	find {{src_dir}} {{inc_dir}} -name '*.[ch]' | xargs clang-format -i

[group('fmt')]
fmt-check:
	find {{src_dir}} {{inc_dir}} -name '*.[ch]' | xargs clang-format --dry-run --Werror

# --- miscellaneous ---

# Generate compile_commands.json for clangd (requires bear)
compile-commands:
	bear -- just build

# Generate HTML documentation in .build/docs/html/
docs:
	#!/usr/bin/env bash
	set -euo pipefail
	mkdir -p {{build_dir}}/docs
	doxygen - <<-'DOXYEOF'
		PROJECT_NAME     = {{name}}
		INPUT            = {{src_dir}} {{inc_dir}}
		OUTPUT_DIRECTORY = {{build_dir}}/docs
		GENERATE_HTML    = YES
		GENERATE_LATEX   = NO
		RECURSIVE        = YES
		EXTRACT_ALL      = YES
		QUIET            = YES
	DOXYEOF
	echo "docs: {{build_dir}}/docs/html/index.html"

# Pack include/ and built libs into a tarball for use in a binary project.
# Usage: just pack [output.tar.gz [dirname]]
#   just pack                        -> mylib.tar.gz, extracts into cwd
#   just pack mylib.tar.gz mylib     -> extracts into mylib/ subdirectory
pack output="" prefix="":
	#!/usr/bin/env bash
	set -euo pipefail
	just static
	just dynamic
	out="{{output}}"
	[[ -z "$out" ]] && out="{{name}}.tar.gz"
	stage=$(mktemp -d)
	[[ -d "$stage" ]] || { echo "error: mktemp failed" >&2; exit 1; }
	trap 'rm -rf "$stage"' EXIT
	pfx="{{prefix}}"
	root="$stage${pfx:+/$pfx}"
	mkdir -p "$root/include" "$root/lib/static" "$root/lib/dynamic"
	cp -r {{inc_dir}}/. "$root/include/"
	[[ -f "{{build_dir}}/lib{{name}}.a"     ]] && cp "{{build_dir}}/lib{{name}}.a"     "$root/lib/static/"
	[[ -f "{{build_dir}}/lib{{name}}.so"    ]] && cp "{{build_dir}}/lib{{name}}.so"    "$root/lib/dynamic/"
	[[ -f "{{build_dir}}/lib{{name}}.dylib" ]] && cp "{{build_dir}}/lib{{name}}.dylib" "$root/lib/dynamic/"
	tar -czf "$out" -C "$stage" "${pfx:-.}"
	echo "packed: $out"

clean:
	rm -rf {{build_dir}}
