SHELL := /bin/bash

# Project-specific settings
PROJECT := dishtiny
EMP_DIR := third-party/Empirical/include/emp

DISHTINY_HASH := $(shell git rev-parse --short HEAD)
DISHTINY_DIRTY := $(shell \
		( git diff-index --quiet HEAD -- && echo "-clean" || echo "-dirty" ) \
		| tr -d '\040\011\012\015' \
	)
DISHTINY_SOURCE_DIR := $(shell test -d /opt/dishtiny && echo /opt/dishtiny || pwd)
# to compile different metrics/selecctors
# make ARGS="-DMETRIC=streak -DSELECTOR=roulette"

# Flags to use regardless of compiler
CFLAGS_all := $(CXXFLAGS) -std=c++17 -Wall -Wno-unused-function -Wno-unused-private-field -Wno-empty-body \
	-Iinclude -Ithird-party/ \
	-DDISHTINY_HASH_=$(DISHTINY_HASH)$(DISHTINY_DIRTY) \
	-DDISHTINY_SOURCE_DIR_=$(DISHTINY_SOURCE_DIR)\
	-fno-signaling-nans -fno-trapping-math \
	$(ARGS)

# Native compiler information
DISH_MPICXX ?= mpicxx.mpich

IS_CLANG := $(shell ($(DISH_MPICXX) --version | grep -q clang); echo $$?)
ifeq (${IS_CLANG},0)
	OMP_FLAG := -fopenmp=libomp
	OMP_LINKER_FLAG := -fopenmp=libiomp5
	# adapted from https://unix.stackexchange.com/a/530726
	CFLAGS_march_native := -march=$(shell cat /sys/devices/cpu/caps/pmu_name)
else
	OMP_FLAG := -fopenmp
	OMP_LINKER_FLAG := -fopenmp
	# adapted from https://stackoverflow.com/a/18963118
	CFLAGS_march_native := $(shell g++ -\#\#\# -E - -march=native 2>&1 | sed -r '/cc1/!d;s/(")|(^.* - )//g' )
endif

CFLAGS_nat := -O3 $(CFLAGS_march_native) -flto -DNDEBUG $(CFLAGS_all) $(OMP_FLAG)
CFLAGS_nat_production := $(CFLAGS_nat) -g -fdebug-prefix-map=$(PWD)=.
CFLAGS_nat_ndata = $(CFLAGS_nat) -DNDATA
CFLAGS_nat_debug := -g -DEMP_TRACK_MEM -D_GLIBCXX_DEBUG -D_LIBCPP_DEBUG $(OMP_FLAG) $(CFLAGS_all)
CFLAGS_nat_sanitize := -fsanitize=address -fsanitize=undefined $(CFLAGS_nat_debug)
CFLAGS_nat_profile := -pg -DNDEBUG $(OMP_FLAG) $(CFLAGS_all)

# Emscripten compiler information
CXX_web := emcc
OFLAGS_web_all := $(CXXFLAGS) -Wno-mismatched-tags -Wno-empty-body -s USE_ZLIB=1 -s USE_LIBLZMA=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" -s TOTAL_MEMORY=671088640 --js-library $(EMP_DIR)/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback', '_empDoCppCallback']" -s NO_EXIT_RUNTIME=1 -s ABORTING_MALLOC=0 -I/usr/lib/x86_64-linux-gnu/openmpi/include/
OFLAGS_web_pthread := -s USE_PTHREADS=1 -s PROXY_TO_PTHREAD=1
OFLAGS_web := -O3 -DNDEBUG -s DISABLE_EXCEPTION_CATCHING=1
OFLAGS_web_debug := -g4 -Oz -Wno-dollar-in-identifier-extension -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2 -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2 -D_GLIBCXX_DEBUG -D_LIBCPP_DEBUG -fexceptions

CFLAGS_web := $(CFLAGS_all) $(OFLAGS_web) $(OFLAGS_web_all)
CFLAGS_web_pthread := $(CFLAGS_all) $(OFLAGS_web) $(OFLAGS_web_pthread) $(OFLAGS_web_all)

default: $(PROJECT)
native: $(PROJECT)
production: $(PROJECT)
web: $(PROJECT).js web/index.html
web-no-pthread: $(PROJECT)-no-pthread.js web/index.html
web-deploy: web web-no-pthread

all: $(PROJECT) $(PROJECT).js web/index.html

debug: CFLAGS_nat := $(CFLAGS_nat_debug)
debug: $(PROJECT)

production: CFLAGS_nat := $(CFLAGS_nat_production)
production: $(PROJECT)

sanitize: CFLAGS_nat := $(CFLAGS_nat_sanitize)
sanitize: $(PROJECT)

debug-web: CFLAGS_web := $(CFLAGS_web) $(OFLAGS_web_debug)
debug-web: CFLAGS_web_pthread := $(CFLAGS_web_pthread) $(OFLAGS_web_debug)
debug-web: $(PROJECT).js web/index.html

web-debug: debug-web

debug-web-no-pthread: CFLAGS_web := $(CFLAGS_web_debug)
debug-web-no-pthread: $(PROJECT)-no-pthread.js web/index.html

web-debug-no-pthread: debug-web-no-pthread

# see https://stackoverflow.com/a/57760267 RE: -lstdc++fs
$(PROJECT): source/native.cpp include/
	@echo DISH_MPICXX $(DISH_MPICXX)
	@echo MPICH_CXX $(MPICH_CXX)
	@echo OMPI_CXX $(OMPI_CXX)
	# use ccache for compile stage
	time ccache $(DISH_MPICXX) $(CFLAGS_nat) -c source/native.cpp
	# perform link stage
	time $(DISH_MPICXX) $(CFLAGS_nat) -o run$(PROJECT) native.o $(OMP_LINKER_FLAG) -lstdc++fs -lmetis -lz -lcurl -lsfml-graphics -ldw -llzma
	@echo To build the web version use: make web

$(PROJECT).js: source/web.cpp include/
	cd third-party/emsdk && . ./emsdk_env.sh && cd - && $(CXX_web) $(CFLAGS_web_pthread) source/web.cpp -o web/$(PROJECT).js

$(PROJECT)-no-pthread.js: source/web.cpp include/
	cd third-party/emsdk && . ./emsdk_env.sh && cd - && $(CXX_web) $(CFLAGS_web) source/web.cpp -o web/$(PROJECT)-no-pthread.js

web/index.html: web/includes
	python3 web/make_html.py

docs:
	$(MAKE) html -C docs/

serve:
	python3 -m http.server

docs/_build/doc-coverage.json:
	$(MAKE) coverage -C docs/

documentation-coverage-badge.json: docs/_build/doc-coverage.json
	python3 ci/parse_documentation_coverage.py docs/_build/doc-coverage.json > web/documentation-coverage-badge.json

version-badge.json:
	python3 ci/parse_version.py .bumpversion.cfg > web/version-badge.json

doto-badge.json:
	python3 ci/parse_dotos.py $$(./ci/grep_dotos.sh) > web/doto-badge.json

badges: documentation-coverage-badge.json version-badge.json doto-badge.json

clean:
	rm -rf run$(PROJECT) web/$(PROJECT).js web/*.js.map web/*.js.map *~ source/*.o web/*.wasm web/*.wast coverage_include web/*.json native.o.ccache*
	$(MAKE) clean -C docs/
	$(MAKE) clean -C fuzzing/
	$(MAKE) clean -C microbenchmarks/
	$(MAKE) clean -C tests/

test-source: debug web
	timeout 90 ./rundishtiny -N_CELLS 100 2>&1 >/dev/null | tee /dev/stderr | grep -q '>> update ' && echo 'matched!' || exit 1
	npm install .
	echo "const puppeteer = require('puppeteer'); var express = require('express'); var app = express(); app.use(express.static('web')); app.listen(3000); express.static.mime.types['wasm'] = 'application/wasm'; function sleep(millis) { return new Promise(resolve => setTimeout(resolve, millis)); } async function run() { const browser = await puppeteer.launch({ headless: true, args: [ '--disable-gpu', '--disable-dev-shm-usage', '--disable-setuid-sandbox', '--no-sandbox' ] }); const page = await browser.newPage(); page.on('console', msg => console.log(msg.text())); await page.goto('http://localhost:3000/index.html'); await sleep(30000); await page.content(); browser.close(); process.exit(0); } run();" | node | tee /dev/stderr | grep -q "web viewer load complete" && echo "matched!"|| exit 1
	echo "const puppeteer = require('puppeteer'); var express = require('express'); var app = express(); app.use(express.static('web')); app.listen(3000); express.static.mime.types['wasm'] = 'application/wasm'; function sleep(millis) { return new Promise(resolve => setTimeout(resolve, millis)); } async function run() { const browser = await puppeteer.launch({ headless: true, args: [ '--disable-gpu', '--disable-dev-shm-usage', '--disable-setuid-sandbox', '--no-sandbox' ] }); const page = await browser.newPage(); await page.goto('http://localhost:3000/index.html'); await sleep(30000); const html = await page.content(); console.log(html); browser.close(); process.exit(0); } run();" | node | tee /dev/stderr | grep -q "Kin Group ID" && echo "matched!" ||  exit 1

tests:
	$(MAKE) -C tests/
	$(MAKE) opt -C tests/
	$(MAKE) fulldebug -C tests/

coverage:
	$(MAKE) coverage -C tests/

install-test-dependencies:
	git submodule update --init && cd third-party && bash ./install_emsdk.sh && bash ./install_force_cover.sh

.PHONY: all
.PHONY: clean
.PHONY: cleanall
.PHONY: cleanlogs
.PHONY: debug
.PHONY: debug-web
.PHONY: default
.PHONY: docs
.PHONY: documentation-coverage
.PHONY: documentation-coverage-badge.json
.PHONY: doto-badge.json
.PHONY: install-test-dependencies
.PHONY: native
.PHONY: ndata
.PHONY: profile
.PHONY: sanitize
.PHONY: serve
.PHONY: static
.PHONY: test-source
.PHONY: tests
.PHONY: version-badge.json
.PHONY: web
.PHONY: web-debug