# FaissEx NIF Build
#
# Environment variables (set via mix.exs make_env):
#   FAISS_PREFIX   - path to pre-installed FAISS (skips building from source)
#   FAISS_GIT_REPO - FAISS git repository URL (ignored when FAISS_PREFIX is set)
#   FAISS_GIT_REV  - FAISS git revision/tag to build (ignored when FAISS_PREFIX is set)
#   USE_CUDA       - "true" to enable GPU support
#   MIX_APP_PATH   - set by elixir_make

FAISS_GIT_REPO ?= https://github.com/facebookresearch/faiss.git
FAISS_GIT_REV  ?= v1.10.0
USE_CUDA       ?= false
FAISS_OPT_LEVEL ?= generic

# Paths
CACHE_DIR    := $(HOME)/.cache/faiss_ex
FAISS_SRC    := $(CACHE_DIR)/faiss-$(subst /,_,$(FAISS_GIT_REV))
FAISS_BUILD  := $(FAISS_SRC)/build
PRIV_DIR     := $(MIX_APP_PATH)/priv
LIB_DIR      := $(PRIV_DIR)/lib

# Erlang
ERTS_INCLUDE_DIR ?= $(shell erl -noshell -eval 'io:format("~ts/erts-~ts/include", [code:root_dir(), erlang:system_info(version)])' -s init stop)

# Platform
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)

ifeq ($(UNAME_S),Darwin)
  NPROC := $(shell sysctl -n hw.ncpu)
  SHARED_EXT := dylib
else
  NPROC := $(shell nproc)
  SHARED_EXT := so
endif

# NIF Compiler flags (prefixed to avoid leaking into cmake)
CC ?= cc
NIF_CFLAGS := -std=c11 -O2 -fPIC -Wall -Wextra -Wno-unused-parameter
NIF_CFLAGS += -I$(ERTS_INCLUDE_DIR)

NIF_LDFLAGS := -shared
NIF_LDFLAGS += -L$(LIB_DIR)
NIF_LDFLAGS += -lfaiss_c -lfaiss
NIF_LDFLAGS += -lstdc++

ifeq ($(UNAME_S),Darwin)
  NIF_LDFLAGS += -undefined dynamic_lookup
  NIF_LDFLAGS += -Wl,-rpath,@loader_path/lib
else
  NIF_LDFLAGS += -Wl,-rpath,'$$ORIGIN/lib'
endif

ifeq ($(USE_CUDA),true)
  NIF_CFLAGS += -DFAISS_GPU_ENABLED
endif

# ========== System FAISS vs build from source ==========

ifdef FAISS_PREFIX
  # Use pre-installed FAISS: headers from FAISS_PREFIX/include, libs from FAISS_PREFIX/lib
  NIF_CFLAGS += -I$(FAISS_PREFIX)/include
  SYSTEM_LIB_DIR := $(FAISS_PREFIX)/lib
else
  # Build from source: headers from the cloned source tree
  NIF_CFLAGS += -I$(FAISS_SRC)
endif

# CMake flags (only used when building from source)
CMAKE_FLAGS := -DFAISS_ENABLE_C_API=ON
CMAKE_FLAGS += -DFAISS_ENABLE_PYTHON=OFF
CMAKE_FLAGS += -DBUILD_TESTING=OFF
CMAKE_FLAGS += -DBUILD_SHARED_LIBS=ON
CMAKE_FLAGS += -DCMAKE_BUILD_TYPE=Release
CMAKE_FLAGS += -DFAISS_OPT_LEVEL=$(FAISS_OPT_LEVEL)

ifeq ($(USE_CUDA),true)
  CMAKE_FLAGS += -DFAISS_ENABLE_GPU=ON
else
  CMAKE_FLAGS += -DFAISS_ENABLE_GPU=OFF
endif

# macOS Apple Silicon: no MKL, need OpenMP hints
ifeq ($(UNAME_S),Darwin)
ifeq ($(UNAME_M),arm64)
  CMAKE_FLAGS += -DFAISS_ENABLE_MKL=OFF
  HOMEBREW_PREFIX ?= /opt/homebrew
  CMAKE_FLAGS += -DOpenMP_ROOT=$(HOMEBREW_PREFIX)/opt/libomp
  CMAKE_FLAGS += -DCMAKE_CXX_FLAGS="-Xclang -fopenmp -I$(HOMEBREW_PREFIX)/opt/libomp/include"
  CMAKE_FLAGS += -DCMAKE_EXE_LINKER_FLAGS="-L$(HOMEBREW_PREFIX)/opt/libomp/lib -lomp"
  CMAKE_FLAGS += -DCMAKE_SHARED_LINKER_FLAGS="-L$(HOMEBREW_PREFIX)/opt/libomp/lib -lomp"
endif
endif

# Targets
NIF_SO := $(PRIV_DIR)/libfaiss_ex.so

.PHONY: all clean

ifdef FAISS_PREFIX
# ========== System FAISS path ==========

all: $(NIF_SO)

# Copy system libs to priv/lib/ so the NIF can find them at runtime
$(LIB_DIR)/libfaiss.$(SHARED_EXT):
	@echo "==> Copying system libfaiss from $(SYSTEM_LIB_DIR)..."
	mkdir -p $(LIB_DIR)
	cp $(SYSTEM_LIB_DIR)/libfaiss.$(SHARED_EXT) $@
ifeq ($(UNAME_S),Darwin)
	install_name_tool -id @rpath/libfaiss.$(SHARED_EXT) $@
endif

$(LIB_DIR)/libfaiss_c.$(SHARED_EXT): $(LIB_DIR)/libfaiss.$(SHARED_EXT)
	@echo "==> Copying system libfaiss_c from $(SYSTEM_LIB_DIR)..."
	mkdir -p $(LIB_DIR)
	cp $(SYSTEM_LIB_DIR)/libfaiss_c.$(SHARED_EXT) $@
ifeq ($(UNAME_S),Darwin)
	install_name_tool -id @rpath/libfaiss_c.$(SHARED_EXT) $@
	install_name_tool -change @rpath/libfaiss.$(SHARED_EXT) @loader_path/libfaiss.$(SHARED_EXT) $@
endif

$(NIF_SO): c_src/faiss_ex_nif.c $(LIB_DIR)/libfaiss_c.$(SHARED_EXT) $(LIB_DIR)/libfaiss.$(SHARED_EXT)
	@echo "==> Compiling NIF (using system FAISS at $(FAISS_PREFIX))..."
	mkdir -p $(PRIV_DIR)
	$(CC) $(NIF_CFLAGS) $< -o $@ $(NIF_LDFLAGS)

else
# ========== Build from source path ==========

all: $(NIF_SO)

# Step 1: Clone FAISS
$(FAISS_SRC)/.cloned:
	@echo "==> Cloning FAISS $(FAISS_GIT_REV)..."
	mkdir -p $(CACHE_DIR)
	git clone --depth 1 --branch $(FAISS_GIT_REV) $(FAISS_GIT_REPO) $(FAISS_SRC)
	touch $@

# Step 2: Configure FAISS
$(FAISS_BUILD)/Makefile: $(FAISS_SRC)/.cloned
	@echo "==> Configuring FAISS..."
	cd $(FAISS_SRC) && env -u CFLAGS -u LDFLAGS -u CXXFLAGS cmake -B build $(CMAKE_FLAGS)

# Step 3: Build libfaiss
$(FAISS_BUILD)/faiss/libfaiss.$(SHARED_EXT): $(FAISS_BUILD)/Makefile
	@echo "==> Building libfaiss..."
	env -u CFLAGS -u LDFLAGS -u CXXFLAGS $(MAKE) -C $(FAISS_BUILD) -j$(NPROC) faiss

# Step 4: Build libfaiss_c
$(FAISS_BUILD)/c_api/libfaiss_c.$(SHARED_EXT): $(FAISS_BUILD)/faiss/libfaiss.$(SHARED_EXT)
	@echo "==> Building libfaiss_c..."
	env -u CFLAGS -u LDFLAGS -u CXXFLAGS $(MAKE) -C $(FAISS_BUILD) -j$(NPROC) faiss_c

# Step 5: Copy libfaiss to priv/lib/
$(LIB_DIR)/libfaiss.$(SHARED_EXT): $(FAISS_BUILD)/faiss/libfaiss.$(SHARED_EXT)
	@echo "==> Installing libfaiss..."
	mkdir -p $(LIB_DIR)
	cp $< $@
ifeq ($(UNAME_S),Darwin)
	install_name_tool -id @rpath/libfaiss.$(SHARED_EXT) $@
endif

# Step 6: Copy libfaiss_c to priv/lib/
$(LIB_DIR)/libfaiss_c.$(SHARED_EXT): $(FAISS_BUILD)/c_api/libfaiss_c.$(SHARED_EXT) $(LIB_DIR)/libfaiss.$(SHARED_EXT)
	@echo "==> Installing libfaiss_c..."
	mkdir -p $(LIB_DIR)
	cp $< $@
ifeq ($(UNAME_S),Darwin)
	install_name_tool -id @rpath/libfaiss_c.$(SHARED_EXT) $@
	install_name_tool -change @rpath/libfaiss.$(SHARED_EXT) @loader_path/libfaiss.$(SHARED_EXT) $@
endif

# Step 7: Compile NIF
$(NIF_SO): c_src/faiss_ex_nif.c $(LIB_DIR)/libfaiss_c.$(SHARED_EXT) $(LIB_DIR)/libfaiss.$(SHARED_EXT)
	@echo "==> Compiling NIF..."
	mkdir -p $(PRIV_DIR)
	$(CC) $(NIF_CFLAGS) $< -o $@ $(NIF_LDFLAGS)

endif

clean:
	rm -rf $(PRIV_DIR)/libfaiss_ex.so $(LIB_DIR)
