# Load the configure script variables
-include configure.mk

##
## Directories
##

# Structure:
#  gray - top level
#  ├── src - source code
#  ├── doc - documentation
#  ├── tests - integration tests
#  └── build - build artifacts
#      ├── bin - binaries
#      ├── lib - libraries
#      ├── obj - objects
#      └── inc - files to #include
SRCDIR   = src
BUILDDIR = build
SHAREDIR = build/share
BINDIR   = build/bin
LIBDIR   = build/lib
OBJDIR   = build/obj
INCDIR   = build/inc

PREFIX ?= /usr

# Directories that need to be created
DIRS = $(BINDIR) $(OBJDIR) $(INCDIR) $(LIBDIR) $(SHAREDIR)

##
## Files
##

# All Fortran source files
SOURCES = $(shell find '$(SRCDIR)' -name '*.f90')

# All Fortran object
OBJECTS = $(addprefix $(OBJDIR)/,$(notdir $(SOURCES:f90=o)))

# Generated makefiles describing object dependencies
DEPS = $(OBJECTS:o=d)

# Fortran modules to be bundled into libraries
MODULES = $(filter-out $(OBJDIR)/main%,$(OBJECTS))

# Build outputs
GRAY      = $(BINDIR)/gray
LIBGRAY   = $(LIBDIR)/libgray.so
BINARIES  = $(GRAY) $(GRAY)-convert
LIBRARIES = $(LIBGRAY)
MANPAGES  = $(addprefix $(SHAREDIR)/,gray.1 gray-convert.1 gray.ini.5 \
              profiles.txt.5 beamdata.txt.5 magneticdata.txt.5)

##
## Git information (used in the version string)
##

# Short hash of the latest commit
GIT_REV   ?= $(shell git rev-parse --short HEAD)

# Whether the worktree and the latest commit differs
GIT_DIRTY ?= $(shell test -n "$$(git status --porcelain)" && echo "-dirty")

# Source date
ifndef SOURCE_DATE_EPOCH
# Use current date
SOURCE_DATE_EPOCH=$(shell awk 'BEGIN {print srand(srand())}')
endif
DATE=$(shell LC_TIME=C date -d @$(SOURCE_DATE_EPOCH) '+%B %Y')

##
## Fortran compiler and flags
##

LD = $(FC)
FFLAGS   += -I$(INCDIR) -fpic
LDFLAGS  += -L$(LIBDIR)
CPPFLAGS += -DREVISION=\"$(GIT_REV)$(GIT_DIRTY)\" -DPREFIX=\"$(PREFIX)\"

# Compiler-specific flags
ifdef GNU
FFLAGS   += -J$(OBJDIR) -frecursive
FFLAGS   += -fmax-errors=1 -ffree-line-length-none
CPPFLAGS += -DGNU
endif
ifdef INTEL
FFLAGS   += -module $(OBJDIR) -assume recursion -heap-arrays
FFLAGS   += -diag-error-limit=1
CPPFLAGS += -DINTEL
endif

ifdef PARALLEL
FFLAGS  += -ftree-parallelize-loops=$(PARALLEL) -fopt-info-loop
LDFLAGS += -fopenmp
endif

# Sync the output from jobs running in parallel
MAKEFLAGS += --output-sync

ifndef DETERMINISTIC
ifdef AR_DEFAULT_DETERMINISTIC
# Write timestamps to allow partial updates
ARFLAGS = crU
endif
endif

# Static build options
ifdef STATIC
LIBGRAY := $(LIBGRAY:so=a)
LIBRARIES := $(LIBRARIES:so=a)
ifndef SEMISTATIC
LDFLAGS += -static
endif
else
LDFLAGS += -Wl,-rpath='$$ORIGIN/../lib/'
endif

# Debug build options
ifdef DEBUG
FFLAGS += -ggdb -O0 -Wall -Wunused-parameter
# Tricks to detect uninitialised memory
FFLAGS += -finit-integer=7777777 -finit-real=snan -ffpe-trap=invalid
else
# Optimization level 3
FFLAGS += -O3
endif

##
## Targets
##

.PHONY: all clean check install-bin install-doc install docs

# Don't update archives in parallel, it's unsupported
.NOTPARALLEL: $(LIBDIR)/libgray.a

all: $(BINARIES) $(LIBRARIES)

# Remove all generated files
clean:
	rm -r $(BUILDDIR)

# Run all tests
check: $(shell python -Bm tests --list-cases)

# Run a test case
tests.%: $(GRAY)
	python -Bm tests '$@' --binary '$(GRAY)' --buffer

# Install libraries, binaries and documentation
install-bin: $(BINARIES) $(LIBRARIES)
	mkdir -p $(PREFIX)/{bin,lib}
	install -m555 -t $(PREFIX)/bin $(BINDIR)/*
	install -m555 -t $(PREFIX)/lib $(LIBDIR)/*

install-doc: $(SHAREDIR)/doc $(MANPAGES)
	mkdir -p $(PREFIX)/share/{doc/res,man/man{1,5}}
	install -m644 -t $(PREFIX)/share/doc $(SHAREDIR)/doc/manual.*
	install -m644 -t $(PREFIX)/share/doc/res $(SHAREDIR)/doc/res/*
	install -m644 -t $(PREFIX)/share/man/man1 $(SHAREDIR)/*.1
	install -m644 -t $(PREFIX)/share/man/man5 $(SHAREDIR)/*.5

install: install-bin install-doc

# dependencies
$(OBJDIR)/%.o: $(OBJDIR)/%.d

# GRAY binary
$(GRAY): $(OBJDIR)/main.o $(LIBGRAY) | $(BINDIR)
	$(LD) $(LDFLAGS) -o '$@' $< -lgray

# gray-convert binary
$(GRAY)-convert: $(OBJDIR)/main_convert.o $(LIBGRAY) | $(BINDIR)
	$(LD) $(LDFLAGS) -o '$@' $< -lgray

# GRAY shared library
$(LIBDIR)/libgray.so: $(MODULES) | $(LIBDIR)
	$(LD) -shared $(LDFLAGS) -o '$@' $^

# GRAY static library
$(LIBDIR)/libgray.a($(MODULES)): | $(LIBDIR)
$(LIBDIR)/libgray.a: $(LIBDIR)/libgray.a($(MODULES))

# GRAY static library when compiling from JETTO
-include ../include.mk
$(JLIBDIR)/libgray.a: $(LIBDIR)/libgray.a
	mkdir -p $(LIBDIR)
	install -m755 '$<' '$@'

# All documentation
docs: $(SHAREDIR)/doc $(MANPAGES)

$(SHAREDIR)/doc: | $(SHAREDIR)
	+make -C doc
	cp -r doc/build/* $(SHAREDIR)

# Generated man pages
$(SHAREDIR)/gray.1: $(GRAY) doc/man/gray.1 | $(SHAREDIR)
	help2man '$<' -i doc/man/gray.1 -N -n 'beam-tracing code for EC waves' > '$@'
$(SHAREDIR)/gray-convert.1: $(GRAY)-convert | $(SHAREDIR)
	help2man '$<' -N -n 'configuration format converter' > '$@'

$(SHAREDIR)/%: doc/man/%.md $(GRAY) | $(SHAREDIR)
	pandoc -s '$<' -t man \
	  -V date='$(DATE)'                                            \
	  -V footer='$(shell $(GRAY) --version | sed -n 1p)'           \
	  -V author='$(shell $(GRAY) --version | sed -n "3 s/.$$//p")' \
	  -o '$@'

# Visualise the dependency graph
# Note: requires makefile2graph and graphviz
graph.svg: Makefile
	make -Bdn                                               \
	| make2graph -b                                         \
	| awk -F'( -> )| ;|[[]' '                               \
		(/label/ && !/(\.o|bin\/gray)/) {bad[$$1]=1; next}    \
	  /->/ {if (bad[$$1] || bad[$$2]) next}                 \
		{print $$0}'                                          \
	| sed 's/label/fontsize=30,label/g; s/\.o//'            \
	| dot -Tsvg -o '$@'

##
## Generic rules
##

# Rebuild everything if the makefile changed
.EXTRA_PREREQS += Makefile

# Search for source code in vendor/ subdirectory
vpath %.f90 $(SRCDIR):$(SRCDIR)/vendor

# Automatic generation of the Fortran prerequisites
# Note 1: this is needed because gfortran -M flag doesn't properly work;
# Note 2: this assumes matching module/file names and no circular
#         dependencies (ie. the dependency graph is a DAG);
$(OBJDIR)/%.d: %.f90 | $(OBJDIR)
	@printf '$(@:d=o): $< \\\n' > '$@'
	@grep -vE '^[[:blank:]]*use.*(intrinsic|iso_|ifport)' '$<' | \
	sed -nE 's@^[[:blank:]]*use[[:blank:]]+'\
	'([^,[:blank:]&]+).*@$(OBJDIR)/\1.o \\@p' | \
	 sort -u >> '$@'
	@sed -nE 's@^#include "(.+)"@$(INCDIR)/\1 \\@p' '$<' >> '$@'

# Load the generated rules
include $(DEPS)

# Compile a generic Fortran source
$(OBJDIR)/%.o: %.f90 | $(OBJDIR) $(INCDIR)
	$(FC) -cpp $(CPPFLAGS) $(FFLAGS) -c '$<' -o '$@'

# Generate Fortran code from awk script
$(INCDIR)/%.inc: $(SRCDIR)/%.awk | $(INCDIR)
	awk -f '$<' > '$@'

# Create directories
$(DIRS):
	mkdir -p '$@'