# Build libfec on Linux, macOS and on/for 64-bit Windows with MinGW-w64.
#
#   make                 build for the host platform
#   make linux           build Linux libraries
#   make macos           build macOS libraries
#   make windows         build Windows libraries (cross-build on Linux/macOS)
#   make static          build only the static library
#   make shared          build only the shared library
#   make install         install libraries, header and man pages
#   make clean           remove obj/ and lib/

ifeq ($(OS),Windows_NT)
  HOST_OS := windows
else ifeq ($(shell uname -s),Darwin)
  HOST_OS := macos
else
  HOST_OS := linux
endif

BUILD ?= $(HOST_OS)

prefix ?= /usr/local
exec_prefix ?= $(prefix)
libdir ?= $(exec_prefix)/lib
includedir ?= $(prefix)/include
mandir ?= $(prefix)/man
INSTALL ?= install

OBJROOT := obj
OUTROOT := lib
OBJDIR  := $(OBJROOT)/$(BUILD)
OUTDIR  := $(OUTROOT)/$(BUILD)
GENDIR  := $(OBJDIR)/generated
TOOLDIR := $(OBJROOT)/tools/$(HOST_OS)

# Command-line assignments (for example CC=clang) still take precedence.
ifeq ($(BUILD),windows)
  ifeq ($(HOST_OS),windows)
    CROSS_PREFIX ?=
  else
    CROSS_PREFIX ?= x86_64-w64-mingw32-
  endif
  ifeq ($(origin CC),default)
    CC := $(CROSS_PREFIX)gcc
  endif
  ifeq ($(origin AR),default)
    AR := $(CROSS_PREFIX)ar
  endif
  ifeq ($(origin RANLIB),undefined)
    RANLIB := $(CROSS_PREFIX)ranlib
  endif
  SHARED_LIB := $(OUTDIR)/libfec.dll
  IMPORT_LIB := $(OUTDIR)/libfec.dll.a
  SHARED_FLAGS := -shared -Wl,--out-implib,$(IMPORT_LIB)
  PIC_CFLAGS :=
  REBIND :=
else ifeq ($(BUILD),linux)
  ifeq ($(origin CC),default)
    CC := gcc
  endif
  ifeq ($(origin AR),default)
    AR := ar
  endif
  ifeq ($(origin RANLIB),undefined)
    RANLIB := ranlib
  endif
  SHARED_LIB := $(OUTDIR)/libfec.so
  IMPORT_LIB :=
  SHARED_FLAGS := -shared -Wl,-soname,libfec.so
  PIC_CFLAGS := -fPIC
  REBIND := ldconfig
else ifeq ($(BUILD),macos)
  ifeq ($(origin CC),default)
    CC := cc
  endif
  ifeq ($(origin AR),default)
    AR := ar
  endif
  ifeq ($(origin RANLIB),undefined)
    RANLIB := ranlib
  endif
  SHARED_LIB := $(OUTDIR)/libfec.dylib
  IMPORT_LIB :=
  SHARED_FLAGS := -dynamiclib -install_name libfec.dylib
  PIC_CFLAGS := -fPIC
  REBIND :=
else
  $(error Unsupported BUILD='$(BUILD)'; use BUILD=linux, BUILD=macos or BUILD=windows)
endif

ifeq ($(origin HOSTCC),undefined)
  HOSTCC := gcc
endif

CPPFLAGS ?=
CFLAGS ?= -O2 -Wall -Wno-implicit-int -Wno-implicit-function-declaration -Wno-int-conversion
LDFLAGS ?=
LDLIBS ?= -lm
HOSTCFLAGS ?= -O2

STATIC_LIB := $(OUTDIR)/libfec.a
TEST_EXEEXT := $(if $(filter windows,$(BUILD)),.exe)
INSTALL_LIBS := $(STATIC_LIB) $(SHARED_LIB) $(IMPORT_LIB)

SOURCES := \
	cpu_mode_generic.c fec.c sim.c \
	viterbi27.c viterbi27_port.c \
	viterbi29.c viterbi29_port.c \
	viterbi615.c viterbi615_port.c \
	encode_rs_char.c encode_rs_int.c encode_rs_8.c \
	decode_rs_char.c decode_rs_int.c decode_rs_8.c \
	init_rs_char.c init_rs_int.c \
	encode_rs_ccsds.c decode_rs_ccsds.c \
	dotprod.c dotprod_port.c \
	peakval.c peakval_port.c \
	sumsq.c sumsq_port.c

GENERATED_SOURCES := $(GENDIR)/ccsds_tab.c $(GENDIR)/ccsds_tal.c
OBJECTS := $(addprefix $(OBJDIR)/,$(SOURCES:.c=.o)) \
	$(OBJDIR)/ccsds_tab.o $(OBJDIR)/ccsds_tal.o
TESTS := vtest27 vtest29 vtest615 rstest dtest sumsq_test peaktest
TEST_PROGRAMS := $(addprefix $(OUTDIR)/,$(addsuffix $(TEST_EXEEXT),$(TESTS)))
TEST_OBJECTS := $(addprefix $(OBJDIR)/,$(addsuffix .o,$(TESTS)))
DEPS := $(OBJECTS:.o=.d) $(TEST_OBJECTS:.o=.d)

GEN_CCSDS := $(TOOLDIR)/gen_ccsds$(if $(filter windows,$(HOST_OS)),.exe)
GEN_TAL   := $(TOOLDIR)/gen_ccsds_tal$(if $(filter windows,$(HOST_OS)),.exe)
HOST_GEN_OBJECTS := $(TOOLDIR)/gen_ccsds.o $(TOOLDIR)/init_rs_char.o

.DEFAULT_GOAL := all
.SECONDARY: $(TEST_OBJECTS)
.PHONY: all build linux macos windows static shared install test check-tools clean

all: build
build: check-tools static shared
static: $(STATIC_LIB)
shared: $(SHARED_LIB)
install: build
	mkdir -p $(DESTDIR)$(libdir)
	$(INSTALL) -m 644 -p $(INSTALL_LIBS) $(DESTDIR)$(libdir)
ifneq ($(REBIND),)
ifneq ($(DESTDIR),)
	@echo "Skipping $(REBIND) for staged install under DESTDIR=$(DESTDIR)"
else ifeq ($(BUILD),$(HOST_OS))
	$(REBIND)
else
	@echo "Skipping $(REBIND) for cross install BUILD=$(BUILD)"
endif
endif
	mkdir -p $(DESTDIR)$(includedir)
	$(INSTALL) -m 644 -p fec.h $(DESTDIR)$(includedir)
	mkdir -m 0755 -p $(DESTDIR)$(mandir)/man3
	$(INSTALL) -m 644 -p simd-viterbi.3 rs.3 dsp.3 $(DESTDIR)$(mandir)/man3
ifeq ($(BUILD),$(HOST_OS))
test: $(TEST_PROGRAMS)
	$(OUTDIR)/vtest27$(TEST_EXEEXT) -e 3.0 -n 1000 -v
	$(OUTDIR)/vtest27$(TEST_EXEEXT)
	$(OUTDIR)/vtest29$(TEST_EXEEXT) -e 2.5 -n 1000 -v
	$(OUTDIR)/vtest29$(TEST_EXEEXT)
	$(OUTDIR)/vtest615$(TEST_EXEEXT) -e 1.0 -n 100 -v
	$(OUTDIR)/vtest615$(TEST_EXEEXT)
	$(OUTDIR)/rstest$(TEST_EXEEXT)
	$(OUTDIR)/dtest$(TEST_EXEEXT)
	$(OUTDIR)/sumsq_test$(TEST_EXEEXT)
	$(OUTDIR)/peaktest$(TEST_EXEEXT)
else
test:
	@echo "Error: tests can only run for the host platform ($(HOST_OS)); requested BUILD=$(BUILD)."
	@exit 1
endif

# Explicit platform aliases are recursive so their toolchains and paths are
# resolved with the requested BUILD value from the start.
linux:
	$(MAKE) BUILD=linux build

macos:
	$(MAKE) BUILD=macos build

windows:
	$(MAKE) BUILD=windows build

check-tools:
	@command -v $(CC) >/dev/null 2>&1 || { \
		echo "Error: compiler '$(CC)' was not found."; \
		echo "For Windows builds install MinGW-w64 or use an MSYS2 MinGW 64-bit shell."; \
		exit 1; \
	}
	@command -v $(HOSTCC) >/dev/null 2>&1 || { \
		echo "Error: host compiler '$(HOSTCC)' was not found."; \
		exit 1; \
	}

$(STATIC_LIB): $(OBJECTS) | $(OUTDIR)
	$(AR) rcs $@ $^
	$(RANLIB) $@

$(SHARED_LIB): $(OBJECTS) | $(OUTDIR)
	$(CC) $(SHARED_FLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)

$(OUTDIR)/%$(TEST_EXEEXT): $(OBJDIR)/%.o $(STATIC_LIB) | $(OUTDIR)
	$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)

$(OBJDIR)/%.o: %.c | $(OBJDIR)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(PIC_CFLAGS) -MMD -MP -I. -c -o $@ $<

$(OBJDIR)/ccsds_tab.o: $(GENDIR)/ccsds_tab.c | $(OBJDIR)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(PIC_CFLAGS) -MMD -MP -I. -c -o $@ $<

$(OBJDIR)/ccsds_tal.o: $(GENDIR)/ccsds_tal.c | $(OBJDIR)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(PIC_CFLAGS) -MMD -MP -I. -c -o $@ $<

$(GENDIR)/ccsds_tab.c: $(GEN_CCSDS) | $(GENDIR)
	$(GEN_CCSDS) > $@

$(GENDIR)/ccsds_tal.c: $(GEN_TAL) | $(GENDIR)
	$(GEN_TAL) > $@

$(GEN_CCSDS): $(HOST_GEN_OBJECTS) | $(TOOLDIR)
	$(HOSTCC) -o $@ $^

$(GEN_TAL): gen_ccsds_tal.c | $(TOOLDIR)
	$(HOSTCC) $(HOSTCFLAGS) -I. -o $@ $<

$(TOOLDIR)/gen_ccsds.o: gen_ccsds.c char.h rs-common.h fec.h | $(TOOLDIR)
	$(HOSTCC) $(HOSTCFLAGS) -I. -c -o $@ $<

$(TOOLDIR)/init_rs_char.o: init_rs_char.c char.h rs-common.h init_rs.h fec.h | $(TOOLDIR)
	$(HOSTCC) $(HOSTCFLAGS) -I. -c -o $@ $<

$(OBJDIR) $(OUTDIR) $(GENDIR) $(TOOLDIR):
	mkdir -p $@

clean:
	-rm -rf $(OBJROOT) $(OUTROOT)

-include $(DEPS)
