# Makefile for the project.
# Provides targets for building, running, benchmarking, testing, and visualization.
#
# Main commands:
#   make run         run the simulation, producing a CSV file
#                    containing pedestrian states at each iteration
#   make video       generate a video of the simulation
#   make run-bench   run benchmark measurements
#   make test        run all unit tests
#
# Default parameters can be overridden from the shell, as follows:
# (parameters can be provided selectively and in any order)
#
#   make run N_P=<num_ped> N_ITER=<num_iter> CSV=<out.csv>
#   make video N_P=<num_ped> N_ITER=<num_iter> CSV=<out.csv> MP4=<video.mp4>
#   make run-bench N_P=<num_ped> N_ITER=<num_iter>


# Select the C++ compiler.
# Prefer g++ when available, otherwise fall back to clang++.
CXX := $(shell command -v g++ || command -v clang++)

ifeq ($(CXX),)
$(error No C++ compiler found. Please install g++ or clang++)
endif

ARCH := $(shell uname -m)

# Prepend sudo on ARM machines when benchmarking.
# This is needed to access the PMU performance counters.
ifeq ($(ARCH),arm64)
SUDO_BENCH := sudo
else ifeq ($(ARCH),aarch64)
SUDO_BENCH := sudo
else
SUDO_BENCH :=
endif

# Compiler flags:
#   -O3            enable high-level optimization
#   -march=native  optimize for the current machine
#   -std=c++23     use the C++23 standard
#   -Wall -Wextra  enable useful warnings
#   -flto          enable link-time optimization across .cpp files, allowing
#                  cross-file optimizations such as function inlining
CXXFLAGS := -O3 -march=native -std=c++23 -Wall -Wextra -flto

VARIANT ?= baseline

VARIANT_SRC := variant_$(VARIANT).cpp

SRC := main.cpp $(VARIANT_SRC)
HEADERS := board.hpp forces.hpp pedestrian.hpp vector2d.hpp constants.hpp social_force.hpp

TEST_SRC := test_vector2d.cpp test_board.cpp test_forces.cpp $(VARIANT_SRC)
TEST_TARGET := run_tests
TEST_LIBS := -lgtest -lgtest_main -pthread

TARGET := main_$(VARIANT)
SIZES_TARGET := sizes


# Default simulation parameters and output files.
# These can be overridden from the make command line; see examples above.
N_P ?= 60              # Default number of pedestrians.
N_ITER ?= 4000         # Default number of simulation iterations.
CSV ?= positions.csv   # Default CSV output file.
MP4 ?= simulation.mp4  # Default visualization video output file.

# Enable/disable non-reproducible random seeding.
# Default: RANDOM_SEED=0 uses the fixed seed for reproducibility.
# Use RANDOM_SEED=1 to seed from std::random_device.
RANDOM_SEED ?= 0

# Pass random-seeding mode to the C++ code.
CXXFLAGS += -DRANDOM_SEED=$(RANDOM_SEED)

# Benchmark suite parameters.
BENCH_SIZES ?= 50 100 200 500 1000 2000 5000 10000
BENCH_ITER ?= 1000

# Verification parameters
REFERENCE_CSV ?= reference/baseline_n60_i4000.csv
REFERENCE_N_P ?= 60
REFERENCE_N_ITER ?= 4000
VERIFY_CSV := verify_$(VARIANT).csv

.PHONY: all main run bench run-bench bench-suite video sizes asm test clean clean-all verify

all: main

# make main : compile the main executable with benchmarking disabled.
main: clean
	$(CXX) $(CXXFLAGS) $(SRC) -o $(TARGET)

# make run : compile the code and run the simulation without generating a video.
run: main
	./$(TARGET) $(N_P) $(N_ITER) $(CSV)

#   make verify                     # sanity check (baseline vs reference)
#   make verify VARIANT=v1_cutoff   # check a specific variant
verify: main
	./$(TARGET) $(REFERENCE_N_P) $(REFERENCE_N_ITER) $(VERIFY_CSV)
	@if  python3 verify.py $(REFERENCE_CSV) $(VERIFY_CSV); then \
		echo "PASS: variant '$(VARIANT)' matches $(REFERENCE_CSV)"; \
		rm -f $(VERIFY_CSV); \
	else \
		echo "FAIL: variant '$(VARIANT)' differs from $(REFERENCE_CSV) (kept $(VERIFY_CSV) for inspection)"; \
		exit 1; \
	fi

# make bench : compile the main executable with benchmarking enabled.
bench: CXXFLAGS += -DBENCHMARK=1
bench: clean
	$(SUDO_BENCH) $(CXX) $(CXXFLAGS) $(SRC) -o $(TARGET)

# make run-bench : compile and run benchmark measurements.
run-bench: bench
	$(SUDO_BENCH) ./$(TARGET) $(N_P) $(N_ITER)

# make bench-suite : compile once and run benchmarks across multiple pedestrian counts.
# Results are printed to stdout; redirect to a file to save them.
#   make bench-suite > benchmarks/baseline.txt
#   make bench-suite VARIANT=v1_cutoff BENCH_SIZES="100 500 1000" > benchmarks/v1_cutoff.txt
bench-suite: bench
	@for n in $(BENCH_SIZES); do \
		echo "=== N_P=$$n N_ITER=$(BENCH_ITER) ==="; \
		$(SUDO_BENCH) ./$(TARGET) $$n $(BENCH_ITER); \
		echo ""; \
	done

# make video : compile the code, run the simulation, and generate the video.
video: run
	python visualization.py $(CSV) $(MP4)

# make sizes : compile the size-check helper.
sizes: clean
	$(CXX) $(CXXFLAGS) sizes.cpp -o $(SIZES_TARGET)

# make test : compile and run all unit tests.
test: clean
	$(CXX) $(CXXFLAGS) $(TEST_SRC) $(TEST_LIBS) -o $(TEST_TARGET)
	./$(TEST_TARGET)

# make asm : compile and generate assembly files.
asm: clean main.s variant_$(VARIANT).s

%.s: %.cpp $(HEADERS)
	$(CXX) -O3 -march=native -std=c++23 -Wall -Wextra -DRANDOM_SEED=$(RANDOM_SEED) -S $< -o $@

# make clean : delete generated files from previous builds.
clean:
	rm -f main_* $(SIZES_TARGET) $(TEST_TARGET) *.o *.s

# make clean-all : delete all generated files from previous builds and runs.
# WARNING: this deletes all *.csv and *.mp4 files in this directory.
# Move any output files you want to keep to another directory before running it.
clean-all: clean
	rm -f *.csv *.mp4