PREFIX ?= /usr/local
DESTDIR ?=

CC ?= gcc
CXX ?= g++
AR ?= ar
INSTALL ?= install

LIB_DIR ?= native/lib
PYTHON ?= python3
VERSION := $(shell $(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
DIST_DIR ?= dist

# no -D_GNU_SOURCE: on glibc >=2.38 it redirects sscanf/strtol to __isoc23_*,
# raising the wheel's glibc floor to 2.38 (manylinux_2_38); without it the
# floor is 2.35 (libmvec atan2 from -ffast-math) -> manylinux_2_35 wheels
# -fvisibility=hidden: only TACT_API-annotated declarations (tact.h) are
# exported from libtact.so; internal helpers and vendored libccd stay private
CFLAGS ?= -W -Wall -O3 -fPIC -ffast-math -funroll-loops -fvisibility=hidden
LDFLAGS ?=
LDLIBS ?= -lm -ldl

TACT_SRC := \
	native/rbd.c \
	native/shape.c \
	native/mpr.c \
	native/narrow.c \
	native/box_box.c \
	native/ray.c \
	native/lcp.c \
	native/tact.c \
	native/bin.c \
	native/render.c

TACT_HEADERS := native/tact.h native/shape.h native/core.h
TACT_LIB := $(LIB_DIR)/libtact.so
BIN_TEST := native/demos/basic/bin-test
BIN_PARITY := local/tests/bin-parity

.PHONY: all clean clean-dist install uninstall package-lib demos test-tools debug sdist wheel-local wheel-manylinux dist-pypi release-preflight

all: package-lib demos

$(TACT_LIB): $(TACT_SRC) $(TACT_HEADERS) | $(LIB_DIR)
	$(CC) $(CFLAGS) -shared -o $@ $(TACT_SRC) $(LDFLAGS) $(LDLIBS)

package-lib: $(TACT_LIB)
	mkdir -p tact/bin
	cp $(TACT_LIB) tact/bin/libtact.so

demos: $(BIN_TEST)

$(BIN_TEST): native/demos/basic/bin_test.c $(TACT_LIB) native/tact.h
	$(CC) -W -Wall -O2 -Inative -o $@ native/demos/basic/bin_test.c -L$(LIB_DIR) -ltact -Wl,-rpath,'$$ORIGIN/../../lib'

test-tools: $(BIN_PARITY)

$(BIN_PARITY): local/tests/bin_parity.c $(TACT_LIB) native/tact.h
	$(CC) -W -Wall -O2 -Inative -o $@ local/tests/bin_parity.c -L$(LIB_DIR) -ltact -Wl,-rpath,'$$ORIGIN/../../native/lib'

debug:
	$(MAKE) -B CFLAGS="-W -Wall -O0 -g -fPIC" $(TACT_LIB) demos

clean-dist:
	rm -rf $(DIST_DIR) build

sdist:
	uv build --sdist

wheel-local:
	uv build --wheel

wheel-manylinux:
	uvx cibuildwheel --platform linux --output-dir $(DIST_DIR)

# A PyPI version number is burned permanently on upload, so the artifact must
# be traceable back to the source that produced it: refuse to build a release
# unless the tree is clean and HEAD is the commit tagged for this version.
release-preflight:
	@git diff-index --quiet HEAD -- || \
		(echo "working tree is dirty; commit before building a release" >&2; exit 1)
	@test -z "$$(git ls-files --others --exclude-standard)" || \
		(echo "untracked files would ship in the sdist; commit or ignore them" >&2; exit 1)
	@git rev-parse -q --verify "refs/tags/v$(VERSION)" >/dev/null || \
		(echo "missing tag v$(VERSION); run: git tag -a v$(VERSION) -m 'pytact $(VERSION)'" >&2; exit 1)
	@test "$$(git rev-parse HEAD)" = "$$(git rev-parse "v$(VERSION)^{commit}")" || \
		(echo "HEAD is not the commit tagged v$(VERSION)" >&2; exit 1)
	@echo "release preflight ok: pytact $(VERSION) at $$(git rev-parse --short HEAD), tagged v$(VERSION)"

dist-pypi: release-preflight clean-dist sdist wheel-manylinux
	@test -f $(DIST_DIR)/pytact-$(VERSION).tar.gz
	@test -n "$$(ls $(DIST_DIR)/pytact-$(VERSION)-py3-none-manylinux_*_x86_64.whl 2>/dev/null)" || \
		(echo "missing manylinux wheel for pytact $(VERSION)" >&2; exit 1)
	ls -lh $(DIST_DIR)/pytact-$(VERSION).tar.gz $(DIST_DIR)/pytact-$(VERSION)-py3-none-manylinux_*_x86_64.whl

$(LIB_DIR):
	mkdir -p $@

install: $(TACT_LIB)
	$(INSTALL) -d $(DESTDIR)$(PREFIX)/include
	$(INSTALL) -d $(DESTDIR)$(PREFIX)/lib
	$(INSTALL) -m 0644 native/tact.h $(DESTDIR)$(PREFIX)/include/tact.h
	$(INSTALL) -m 0755 $(TACT_LIB) $(DESTDIR)$(PREFIX)/lib/libtact.so

uninstall:
	rm -f $(DESTDIR)$(PREFIX)/include/tact.h
	rm -f $(DESTDIR)$(PREFIX)/lib/libtact.so

clean:
	rm -rf $(LIB_DIR) $(BIN_TEST)
