LIBSRC := $(wildcard src/*.f)
BIFOBJ := $(patsubst %.f,%.o, $(LIBSRC))
BINSRC := $(wildcard bin/*.f)
BIFBIN := $(addprefix bin/bifpack-, $(filter-out dataman, $(notdir $(patsubst %.f,%, $(BINSRC)))))
SOLSRC := $(wildcard solvers/*.f)
SOLOBJ := $(patsubst %.f,%.o, $(SOLSRC))
F77    := gfortran -std=legacy
LIBBIF := bifpack
LIBSOL := bifsolv
LIBDIR := lib
MKDIR  := mkdir -p

.SUFFIXES: .o .f

# Compiled -fPIC unconditionally (a small, harmless overhead for the
# static archives below) so the same objects can also be linked into
# the shared libraries further down without a second, PIC-only object
# variant to maintain.
.f.o:
	$(F77) -fPIC -c -o $@ $<

$(LIBDIR):
	$(MKDIR) $(LIBDIR)

# --- Static archives --------------------------------------------------
#
# One archive for all of src/*.f, one for all of solvers/*.f. Safe even
# though, taken together, a small number of same-named routines appear
# more than once across these files (see "Shared libraries" below for
# which, and why) - unlike a shared library, `ar` does not resolve
# symbols when an archive is created, and the final link step for any
# one example only ever extracts whichever single same-named routine
# that example actually calls, never both.
static: $(BIFOBJ) $(SOLOBJ) $(LIBDIR)
	ar cr $(LIBDIR)/lib$(LIBBIF).a $(BIFOBJ)
	ar cr $(LIBDIR)/lib$(LIBSOL).a $(SOLOBJ)

# --- Shared libraries, one per docs/manual.asc §M5 grouping ------------
#
# BIFPACK's own source was never designed to all coexist in one shared
# library: distinct sub-packages independently define internal helper
# routines that happen to share a name (packa2.f's and packb2.f's own,
# unrelated, internal FCNLAR; solvers/shoot.f's own RKF7, a substitute
# for solvers/ode1.f's own RKF7, never meant to be linked alongside
# it). The manual's own §M5 "What sub-package/file is required for
# which purpose?" table documents which files a given problem type
# actually needs together, and no two of those documented groupings
# ever combine two routines of the same name - so building one shared
# library per grouping (rather than one library from every src/*.f
# file at once) avoids every such clash without renaming or otherwise
# editing any upstream file.
#
# Group A (manual §M5, A1-A4): continuation and bifurcation analysis of
# algebraic equations, direct Hopf-point calculation, and the shooting
# approach to boundary-value problems treated as algebraic equations.
# Includes solvers/shoot.f rather than solvers/ode1.f (A4's own
# grouping excludes ode1.f) - group B below is the other way around.
LIBA_SRC := src/packa1.f src/packa2.f src/packa3.f \
            solvers/shoot.f solvers/nole.f solvers/linear.f solvers/eispack.f
LIBA_OBJ := $(patsubst %.f,%.o, $(LIBA_SRC))

$(LIBDIR)/libbifpack_a.so: $(LIBA_OBJ) | $(LIBDIR)
	$(F77) -shared -o $@ $(LIBA_OBJ)

# Group B (manual §M5, B1-B4): continuation and bifurcation analysis of
# two-point ODE boundary-value problems via multiple shooting.
LIBB_SRC := src/packb1.f src/packb2.f src/packb3.f \
            solvers/ode1.f solvers/linear.f
LIBB_OBJ := $(patsubst %.f,%.o, $(LIBB_SRC))

$(LIBDIR)/libbifpack_b.so: $(LIBB_OBJ) | $(LIBDIR)
	$(F77) -shared -o $@ $(LIBB_OBJ)

# Group D (manual §M5, D1-D3): bifurcations of periodic solutions of
# autonomous ODEs, together with the stationary-solution routines
# (packa1.f/packa2.f/packa3.f) and the packon.f driver that ties the
# two together - D3's own grouping, which subsumes D1 and D2.
LIBD_SRC := src/packd.f src/packon.f src/packa1.f src/packa2.f src/packa3.f \
            solvers/ode1.f solvers/eispack.f solvers/nole.f solvers/linear.f
LIBD_OBJ := $(patsubst %.f,%.o, $(LIBD_SRC))

$(LIBDIR)/libbifpack_d.so: $(LIBD_OBJ) | $(LIBDIR)
	$(F77) -shared -o $@ $(LIBD_OBJ)

.PHONY: shared
shared: $(LIBDIR)/libbifpack_a.so $(LIBDIR)/libbifpack_b.so $(LIBDIR)/libbifpack_d.so

# --- capi shared libraries, one per Python sub-package ------------------
#
# Distinct from the plain §M5-grouping libraries above: a shared
# library only fails to *load* (dlopen/ctypes.CDLL) over an unresolved
# symbol it never actually needs at runtime if something eagerly
# checks for one - unlike an ordinary executable link, `ld -shared`'s
# own default is to allow an unresolved symbol through at build time,
# deferring it to load time. libbifpack_a.so above (which still
# includes solvers/shoot.f) compiles cleanly for exactly this reason
# despite shoot.f's own SHOOT calling BC (a boundary-value-problem
# routine, not something A1-A4 ever defines) by bare name - a latent
# problem nothing has hit yet because nothing dlopens libbifpack_a.so
# directly today. capi/algebraic.f90 is validated by actually loading
# the result via ctypes, not merely compiling it, so its own object
# set is chosen to be exactly what capi/algebraic.f90's own call graph
# needs (see capi/algebraic.f90's own comments for why packa2.o/
# packa3.o are required despite Phase 1 only calling CONTA, and why
# solvers/shoot.o is excluded).
CAPI_ALGEBRAIC_SRC := capi/algebraic.f90 \
                       src/packa1.f src/packa2.f src/packa3.f \
                       solvers/nole.f solvers/linear.f solvers/eispack.f
CAPI_ALGEBRAIC_OBJ := $(patsubst %.f90,%.o, $(patsubst %.f,%.o, $(CAPI_ALGEBRAIC_SRC)))

capi/%.o: capi/%.f90
	$(F77) -fPIC -c -o $@ -J capi $<

$(LIBDIR)/libbifpack_capi_algebraic.so: $(CAPI_ALGEBRAIC_OBJ) | $(LIBDIR)
	$(F77) -shared -o $@ $(CAPI_ALGEBRAIC_OBJ)

# capi/periodic_orbits.f90's own call graph (CONTD/HOPF, src/packd.f)
# needs none of packa1-3.f/nole.f - only packd.f itself plus the
# multiple-shooting layer it sits on (solvers/ode1.f's own
# SOLVER/ODEBC1/DATEN/RKF7 and its own linear-algebra helpers,
# solvers/linear.f's BGDECO/BGSOLV) and solvers/eispack.f's own
# EIGVAL (STAPER's own monodromy-matrix eigenvalues) - confirmed by
# grepping packd.f's and ode1.f's own CALL statements, not assumed
# from the §M5 grouping above (which bundles in packa1-3.f too, for
# BIFLAB's own combined interactive use, not needed here).
CAPI_PERIODIC_SRC := capi/periodic_orbits.f90 \
                      src/packd.f solvers/ode1.f solvers/eispack.f solvers/linear.f
CAPI_PERIODIC_OBJ := $(patsubst %.f90,%.o, $(patsubst %.f,%.o, $(CAPI_PERIODIC_SRC)))

$(LIBDIR)/libbifpack_capi_periodic.so: $(CAPI_PERIODIC_OBJ) | $(LIBDIR)
	$(F77) -shared -o $@ $(CAPI_PERIODIC_OBJ)

# capi/boundary_value.f90's own call graph needs packb1.f (CONTB),
# packb2.f (TESTB/SWITB, bifpack_boundary_value_locate_and_switch's
# own routines), and packb3.f (PBP,
# bifpack_boundary_value_primary_bifurcation_points's own routine)
# plus the multiple-shooting layer they all sit on (solvers/ode1.f's
# own SOLVER/ODEBC1/DATEN/RKF7 and its own linear-algebra helpers,
# solvers/linear.f's BGDECO/BGSOLV, which PBP itself also calls
# directly) - no eispack.f, unlike packd (packb has no
# STAPER-equivalent stability routine of its own).
CAPI_BOUNDARY_VALUE_SRC := capi/boundary_value.f90 \
                            src/packb1.f src/packb2.f src/packb3.f \
                            solvers/ode1.f solvers/linear.f
CAPI_BOUNDARY_VALUE_OBJ := $(patsubst %.f90,%.o, $(patsubst %.f,%.o, $(CAPI_BOUNDARY_VALUE_SRC)))

$(LIBDIR)/libbifpack_capi_boundary_value.so: $(CAPI_BOUNDARY_VALUE_OBJ) | $(LIBDIR)
	$(F77) -shared -o $@ $(CAPI_BOUNDARY_VALUE_OBJ)

.PHONY: capi
capi: $(LIBDIR)/libbifpack_capi_algebraic.so $(LIBDIR)/libbifpack_capi_periodic.so $(LIBDIR)/libbifpack_capi_boundary_value.so

# --- bin/*.f: standalone post-processing utilities ---------------------
#
# Operate on BIFPACK's own output-record files (DATBASE and similar);
# link against the same combined static archives as the examples below.
bin/bifpack-%: bin/%.f static
	$(F77) -o $@ -L ./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL) $<

.PHONY: bin
bin: $(BIFBIN)

# bin/dataman.f is deliberately excluded from BIFBIN/bin above (see
# bin/README.md's own "dataman.f" section): unlike the five utilities
# that pattern rule builds, it calls SETUP/FCN directly (confirmed
# directly in its own body) rather than only reading already-written
# output files, so it needs linking against a specific problem file's
# own SETUP/FCN the same way an example's own main program does - see
# the EXAMPLES section below for that identical pattern
# (examples/exa1m2, for instance). This pattern rule does that
# pairing on demand for any PROBLEM-only example file (one that has no
# own main of its own - exa1, exb4, exb2f, exb1f, exd1, exd2 below):
#
#   make examples/exa1-dataman
#
# Not every example file this pattern matches actually works: exa3.f
# and exb13.f are self-contained (their own main already built in, per
# the EXAMPLES table below) - pairing either with dataman.f's own
# PROGRAM MAIN fails to link (duplicate main), the identical reason
# neither has its own dedicated main-pairing target below either.
examples/%-dataman: examples/%.f bin/dataman.f static
	$(F77) -o $@ examples/$*.f bin/dataman.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

# --- Examples ------------------------------------------------------------
#
# One target per docs/manual.asc §M4.2 example program, each linking
# the documented "problem" file (SETUP/FCN[/FCNLIN][/BC][/BCLIN]) with
# its documented "main" - either one of the three generic interactive
# drivers (mainA=examples/maina.f, mainB=examples/mainb.f,
# mainD=examples/maind.f, each built around INTERA/BIFLAB) or a
# dedicated main of the example's own - against the combined static
# archives. §M5 spells out which grouping (A/B/D) each belongs to; see
# the shared-library section above for the same grouping used there.
#
#   target       main            problem     §M5 case
#   -----------  --------------  ----------  --------
#   exa1         maina.f         exa1.f      A1
#   exa1m2       exa1m2.f (own)  exa1.f      A2
#   exa3         exa3.f (self-contained)     A3
#   exb2am       exb2am.f (own)  exb2f.f     A4
#   exb4         mainb.f         exb4.f      B1
#   exb2m        exb2m.f (own)   exb2f.f     B2
#   exb13        exb13.f (self-contained)    B3
#   exb1m        exb1m.f (own)   exb1f.f     B4
#   exd1         maind.f         exd1.f      D3
#   exd2         maind.f         exd2.f      D3
EXAMPLES := examples/exa1 examples/exa1m2 examples/exa3 examples/exb2am \
            examples/exb4 examples/exb2m examples/exb13 examples/exb1m \
            examples/exd1 examples/exd2

examples/exa1: examples/exa1.f examples/maina.f static
	$(F77) -o $@ examples/exa1.f examples/maina.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exa1m2: examples/exa1.f examples/exa1m2.f static
	$(F77) -o $@ examples/exa1.f examples/exa1m2.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exa3: examples/exa3.f static
	$(F77) -o $@ examples/exa3.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exb2am: examples/exb2f.f examples/exb2am.f static
	$(F77) -o $@ examples/exb2f.f examples/exb2am.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exb4: examples/exb4.f examples/mainb.f static
	$(F77) -o $@ examples/exb4.f examples/mainb.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exb2m: examples/exb2f.f examples/exb2m.f static
	$(F77) -o $@ examples/exb2f.f examples/exb2m.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exb13: examples/exb13.f static
	$(F77) -o $@ examples/exb13.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exb1m: examples/exb1f.f examples/exb1m.f static
	$(F77) -o $@ examples/exb1f.f examples/exb1m.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exd1: examples/exd1.f examples/maind.f static
	$(F77) -o $@ examples/exd1.f examples/maind.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

examples/exd2: examples/exd2.f examples/maind.f static
	$(F77) -o $@ examples/exd2.f examples/maind.f -L./$(LIBDIR) -l$(LIBBIF) -l$(LIBSOL)

.PHONY: examples
examples: $(EXAMPLES)

.PHONY: all
all: static shared capi bin examples

# The Python interface's own documentation (docs/) - a Sphinx project,
# built separately from everything above: needs `make capi` already
# done (autodoc imports the real bifpack package) but not `make all`.
# docs/images/ is pre-built and committed, not regenerated here - see
# docs/generate_images.py's own module docstring for why, and how to
# regenerate it by hand after a real behavioural change.
.PHONY: docs
docs: capi
	PYTHONPATH=src sphinx-build -b html docs docs/_build/html

.PHONY: docs-clean
docs-clean:
	$(RM) -r docs/_build

.PHONY: clean
clean:
	$(RM) $(BIFOBJ) $(SOLOBJ) capi/*.o capi/*.mod
	$(RM) examples/BUFFER examples/HEAD examples/INDFILE
	$(RM) examples/OPTIONS examples/START examples/DATONE
	$(RM) examples/DATLIST examples/DATOUT
	$(RM) examples/*.LST examples/*.OUT

# distclean: the whole repository back to a clean checkout, not just
# the Fortran/capi build products `clean` above removes - every path
# named here mirrors .gitignore's own list field for field (deliberate
# duplication, not an oversight: keeps this target free of any git
# invocation, plain rm/find only), so a new category of build artifact
# needs adding in both places if one is ever introduced. Previously
# missed entirely: Python's own __pycache__/.pytest_cache/.hypothesis/
# *.egg-info, and any *-dataman binary built via the pattern rule
# above (not in $(EXAMPLES), so the old $(RM) $(EXAMPLES) line alone
# never caught it).
.PHONY: distclean
distclean: clean docs-clean
	$(RM) $(BIFBIN)
	$(RM) $(EXAMPLES)
	$(RM) examples/*-dataman
	$(RM) -r $(LIBDIR)
	$(RM) -r src/bifpack/*/_lib
	$(RM) -r *.egg-info src/*.egg-info
	$(RM) -r .pytest_cache .hypothesis
	$(RM) -r dist build
	find . -name '__pycache__' -type d -exec $(RM) -r {} +

# sdist: the actual PyPI release artifact (dist/bifpack-*.tar.gz) -
# `pip install`ing it runs setup.py's own custom build_py command on
# the installer's own machine (needs gfortran/GNU Make there, same as
# working from a checkout always has), not this one - nothing here
# needs `make capi` run first.
.PHONY: sdist
sdist:
	python3 -m build --sdist
