OS = $(shell uname)

ifeq ($(filter config, $(MAKECMDGOALS)),)
-include config.mak
endif

INSTALL ?= install
TAR ?= tar
CONFIG_CFLAGS ?= -O3
CONFIG_CFLAGS := $(CONFIG_CFLAGS) $(CFLAGS)

CFLAGS := $(CONFIG_CFLAGS)

# extra flags we don't want to forward to external libs like libhfs/ublio/utf8proc/lzvn
LOCAL_CFLAGS+=-std=gnu11 -D_FILE_OFFSET_BITS=64 -Wall -Wextra -pedantic -Wno-gnu-zero-variadic-macro-arguments -Wno-unused-parameter -Wno-error=type-limits -Wno-tautological-constant-out-of-range-compare
# older versions of gcc/clang need these as well
LOCAL_CFLAGS+=-Wno-missing-field-initializers -Wno-missing-braces

TARGETS = hfsfuse hfsdump

XATTR_NAMESPACE ?= user.
ifeq ($(OS), Darwin)
	APP_FLAGS += -I/usr/local/include
	APP_LIB += -L/usr/local/lib
	XATTR_NAMESPACE = #no namespaces on macOS
	ifeq ($(shell [ -e /usr/local/lib/libosxfuse.dylib ] && echo 1), 1)
		FUSE_FLAGS += -I/usr/local/include/osxfuse
		FUSE_LIB = -losxfuse
	else ifeq ($(shell [ -e /usr/local/lib/libfuse.dylib -o -e /usr/local/lib/libfuse3.dylib ] && echo 1), 1)
		FUSE_FLAGS += -I/usr/local/include -Wno-language-extension-token
	else ifeq ($(shell [ -e /usr/local/lib/libfuse-t.dylib ] && echo 1), 1)
		FUSE_FLAGS += -I/Library/Application\ Support/fuse-t/include/fuse
		FUSE_LIB = -lfuse-t
	else
$(info no FUSE library found, FUSE driver will not be built)
		TARGETS = hfsdump
	endif
else ifeq ($(OS), Haiku)
	CFLAGS += -D_BSD_SOURCE -DB_USE_POSITIVE_POSIX_ERRORS
	APP_LIB += -lbsd -lposix_error_mapper
	FUSE_FLAGS += -I/system/develop/headers/userlandfs/fuse -I/system/develop/headers/bsd
	FUSE_LIB = -L/system/lib/ -luserlandfs_fuse
	PREFIX ?= /boot/home/config/non-packaged
else ifeq ($(OS), FreeBSD)
	APP_FLAGS += -I/usr/local/include
	APP_LIB += -L/usr/local/lib
	FUSE_FLAGS += -I/usr/local/include
else ifeq ($(OS), DragonFly)
	APP_FLAGS += -I/usr/local/include
	APP_LIB += -L/usr/local/lib
	FUSE_FLAGS += -I/usr/local/include
else ifeq ($(OS), OpenBSD)
	APP_FLAGS += -I/usr/local/include -I/usr/local/include/libutf8proc
	APP_LIB += -L/usr/local/lib
else ifeq ($(OS), NetBSD)
	APP_FLAGS += -I/usr/pkg/include
	APP_LIB += -L/usr/pkg/lib -Wl,-R/usr/pkg/lib
	FUSE_FLAGS += -I/usr/pkg/include -L/usr/pkg/lib/ -Wl,-R/usr/pkg/lib
else ifeq ($(OS), SunOS)
	FUSE_FLAGS += -I/usr/include/fuse
else ifeq (MSYS, $(findstring MSYS, $(OS)))
$(info MSYS2 detected, FUSE driver will not be built)
	WITH_UBLIO ?= none
	ifneq (none, $(WITH_UBLIO))
$(warning building with ublio is not supported under MSYS2)
	endif
	TARGETS = hfsdump
else ifeq (MINGW, $(findstring MINGW, $(OS)))
$(info MinGW detected, FUSE driver will not be built)
	WITH_UBLIO ?= none
	ifneq ($(WITH_UBLIO), none)
$(warning building with ublio is not supported under MinGW)
	endif
	APP_LIB += -static
	APP_FLAGS += -D_POSIX_THREAD_SAFE_FUNCTIONS
	TARGETS = hfsdump
else #linux
	ifeq ($(findstring _POSIX_C_SOURCE, $(LOCAL_CFLAGS)),)
		FUSE_FLAGS += -D_GNU_SOURCE #for statx
	endif
endif

PREFIX ?= /usr/local
prefix ?= $(PREFIX)
exec_prefix = $(prefix)
bindir = $(exec_prefix)/bin
libdir = $(exec_prefix)/lib
includedir = $(prefix)/include
pkgconfigdir = $(libdir)/pkgconfig

WITH_UBLIO ?= local
WITH_UTF8PROC ?= local
WITH_LZVN ?= local

CEXPR_TEST_CFLAGS = -Werror-implicit-function-declaration -Wno-unused-value -Wno-missing-braces\
 -Wno-missing-field-initializers -Wno-format-security -Wno-format-nonliteral

ccshellcmd = printf "%s\n" "int main(void){$(1);}" | $(CC) $(if $(CEXPR_CFLAGS),$(CEXPR_CFLAGS),$(CFLAGS)) -xc -fsyntax-only $(CEXPR_TEST_CFLAGS) $(foreach inc,$(2),-include $(inc)) -
parsecexpr = $(shell ! $(call ccshellcmd, $(1), $(2)) > $(if $(VERBOSE),/dev/stdout,/dev/null) 2> $(if $(VERBOSE),/dev/stderr,/dev/null); echo $$?)

echo_features = $(if $(filter showconfig,$(MAKECMDGOALS)),,1)

define cccheck
ifndef $(1)
    $$(if $$(VERBOSE),$$(info Checking $(1) with `$(call ccshellcmd, $(2), $(3))`))
    $(1) := $$(call parsecexpr, $(2), $(3))
    $$(if $$(echo_features),$$(info $(1): $$(if $$(filter $$($(1)),1),yes,no)))
endif
FEATURES+=$(1)
endef

define testcccheck
ifneq ($$(call parsecexpr),1)
    tmp:=$$(VERBOSE)
    VERBOSE=true
    $$(info Unable to use C compiler "$(CC)" for platform-dependent feature detection. Specify these directly to make or by running `make config` and editing $\
           config.mak, otherwise fallbacks will be used. Command:)
    $$(info $$(call ccshellcmd))
    _:=$$(call parsecexpr)
    VERBOSE:=$$(tmp)
endif
endef

non_build_targets = dist version authors uninstall uninstall-lib clean distclean

ifneq ($(filter-out $(non_build_targets),$(or $(MAKECMDGOALS),all)),)
    CEXPR_CFLAGS=$(CFLAGS) $(LIBHFS_CFLAGS)
    $(eval $(call testcccheck))
    $(eval $(call cccheck,HAVE_BEXXTOH_ENDIAN_H,{ be16toh(0); be32toh(0); be64toh(0); },endian.h))
    $(eval $(call cccheck,HAVE_BEXXTOH_SYS_ENDIAN_H,{ be16toh(0); be32toh(0); be64toh(0); },sys/endian.h))
    $(eval $(call cccheck,HAVE_OSBYTEORDER_H,{ OSSwapBigToHostInt16(0); OSSwapBigToHostInt32(0); OSSwapBigToHostInt64(0); },libkern/OSByteOrder.h))

    CEXPR_CFLAGS=$(CFLAGS) $(LOCAL_CFLAGS) $(APP_FLAGS)
    $(eval $(call testcccheck))
    $(eval $(call cccheck,HAVE_BIRTHTIME,{ (struct stat){0}.st_birthtime; },sys/stat.h))
    $(eval $(call cccheck,HAVE_STAT_FLAGS,{ (struct stat){0}.st_flags; },sys/stat.h))
    $(eval $(call cccheck,HAVE_STAT_BLKSIZE,{ (struct stat){0}.st_blksize; },sys/stat.h))
    $(eval $(call cccheck,HAVE_STAT_BLOCKS,{ (struct stat){0}.st_blocks; },sys/stat.h))
    $(eval $(call cccheck,HAVE_VSYSLOG,{ vsyslog(0,(const char*){0},(va_list){0}); },syslog.h stdarg.h))
    $(eval $(call cccheck,HAVE_PREAD,{ pread(0,(void*){0},0,0); },unistd.h))

    $(eval $(call cccheck,HAVE_LZFSE,,lzfse.h))
    $(eval $(call cccheck,HAVE_ZLIB,,zlib.h))

    $(eval $(call cccheck,HAVE_LIBARCHIVE,,archive.h archive_entry.h))

		ifeq ($(HAVE_LIBARCHIVE),1)
			TARGETS += hfstar
		else
$(info libarchive not found, hfstar will not be built)
		endif

    CEXPR_CFLAGS =$(CFLAGS) $(LOCAL_CFLAGS) $(FUSE_FLAGS) -DFUSE_USE_VERSION=30
    $(eval $(call cccheck,HAVE_FUSE3,,fuse3/fuse.h))

		ifeq ($(HAVE_FUSE3),1)
			FUSE_FLAGS += -DFUSE_USE_VERSION=35
			FUSE_LIB ?= -lfuse3
		else
			FUSE_FLAGS += -DFUSE_USE_VERSION=29
			FUSE_LIB ?= -lfuse
		endif

    $(eval $(call cccheck,HAVE_STATX,{ (struct statx){0}; },sys/stat.h))
endif

$(foreach cfg,OS CC AR INSTALL TAR PREFIX WITH_UBLIO WITH_UTF8PROC WITH_LZVN XATTR_NAMESPACE CONFIG_CFLAGS $(FEATURES),$(eval CONFIG:=$(CONFIG)$(cfg)=$$($(cfg))\n))
$(foreach feature,$(FEATURES),$(if $(filter $($(feature)),1),$(eval FEATURE_CFLAGS+=-D$(feature))))

LOCAL_CFLAGS += $(FEATURE_CFLAGS)
LIBHFS_CFLAGS += $(FEATURE_CFLAGS)

LIBS = lib/libhfsuser/libhfsuser.a lib/libhfs/libhfs.a
LIBDIRS = $(abspath $(dir $(LIBS)))
INCLUDE = $(foreach lib,$(LIBDIRS),-iquote $(lib))

ifneq ($(WITH_UBLIO), none)
	APP_FLAGS += -DHAVE_UBLIO
	ifeq ($(WITH_UBLIO), system)
		APP_LIB += -lublio
	else ifeq ($(WITH_UBLIO), local)
		LIBS += lib/ublio/libublio.a
	else
$(error Invalid option "$(WITH_UBLIO)" for WITH_UBLIO. Use one of: none, system, local)
	endif
endif
ifneq ($(WITH_UTF8PROC), none)
	APP_FLAGS += -DHAVE_UTF8PROC
	ifeq ($(WITH_UTF8PROC), system)
		APP_LIB += -lutf8proc
	else ifeq ($(WITH_UTF8PROC), local)
		CFLAGS+=-DUTF8PROC_EXPORTS
		LIBS += lib/utf8proc/libutf8proc.a
	else
$(error Invalid option "$(WITH_UTF8PROC)" for WITH_UTF8PROC. Use one of: none, system, local)
	endif
endif
ifneq ($(WITH_LZVN), none)
	APP_FLAGS += -DHAVE_LZVN
	ifeq ($(WITH_LZVN), system)
		APP_LIB += -lFastCompression
	else ifeq ($(WITH_LZVN), local)
		LIBS += lib/LZVN/libFastCompression.a
	else
$(error Invalid option "$(WITH_LZVN)" for WITH_LZVN. Use one of: none, system, local)
	endif
endif
ifneq ($(WITH_UTHASH), system)
	UTHASH_FLAGS = -Ilib/uthash
endif

APP_LIB+=$(if $(filter $(HAVE_ZLIB),1),-lz)
APP_LIB+=$(if $(filter $(HAVE_LZFSE),1),-llzfse)
APP_LIB+=$(if $(filter $(HAVE_LZVN),1),-lFastCompression)

RELEASE_NAME=hfsfuse
RELEASE_BRANCH=master
GIT_HEAD_SHA=$(shell git rev-parse --short $(RELEASE_BRANCH) 2> /dev/null)
ifneq ($(GIT_HEAD_SHA), )
	VERSION = 0.$(shell git rev-list $(RELEASE_BRANCH) --count)
	RELEASE_NAME := $(RELEASE_NAME)-$(VERSION)
	VERSION_STRING = \"$(VERSION)-$(GIT_HEAD_SHA)\"
	CFLAGS += -DHFSFUSE_VERSION_STRING=$(VERSION_STRING)
else ifeq ($(wildcard src/version.h), )
    $(warning Warning: git repo nor prepackaged version.h found, hfsfuse will be built without version information)
	CFLAGS += -DHFSFUSE_VERSION_STRING=\"omitted\"
endif

define pkg_config_file
Name: libhfsuser
Description: Userspace support library for libhfs.
Version: $(VERSION)
URL: http://github.com/0x09/hfsfuse

Libs: $(APP_LIB) -lhfsuser
Libs.private: $(filter-out -lhfsuser,$(foreach lib,$(LIBS),-l$(subst lib,,$(basename $(notdir $(lib))))))
Cflags: -I$(includedir)
endef

export CONFIG PREFIX prefix bindir libdir includedir DESTDIR CFLAGS LIBDIRS INSTALL pkg_config_file

DEPS = src/hfsfuse.d src/hfsdump.d src/hfstar.d

LDLIBS += $(APP_LIB) -lpthread

vpath %.o src

.PHONY: all clean always_check config showconfig install install-lib lib $(non_build_targets)

all: $(TARGETS)

%.o: CPPFLAGS += $(INCLUDE) -MMD -MP
%.o: CFLAGS += $(LOCAL_CFLAGS)

src/hfsfuse.o: CPPFLAGS += $(FUSE_FLAGS) -DXATTR_NAMESPACE=$(XATTR_NAMESPACE)

lib/libhfs/libhfs.a: CFLAGS := $(LIBHFS_CFLAGS) $(CFLAGS)
lib/libhfsuser/libhfsuser.a: CFLAGS := $(LOCAL_CFLAGS) $(INCLUDE) $(APP_FLAGS) $(CFLAGS)

$(LIBS): always_check
	$(MAKE) -C $(dir $@)

libhfsuser.pc:
	@echo "$$pkg_config_file" > $@

lib: $(LIBS) libhfsuser.pc

hfsfuse: LDLIBS += $(FUSE_LIB)
hfsfuse: src/hfsfuse.o $(LIBS)
	$(CC) -o $@ $(LDFLAGS) $^ $(LDLIBS)
ifeq ($(OS), Darwin)
	if ! otool -l hfsfuse | grep -q LC_RPATH; then install_name_tool -add_rpath /usr/local/lib hfsfuse; fi
endif

hfsdump: src/hfsdump.o $(LIBS)

hfstar: CPPFLAGS += $(APP_FLAGS) $(UTHASH_FLAGS) -DXATTR_NAMESPACE=$(XATTR_NAMESPACE)
hfstar: LDLIBS += -larchive
hfstar: src/hfstar.o $(LIBS)

clean:
	for dir in $(LIBDIRS); do $(MAKE) -C $$dir clean; done
	$(RM) src/hfsfuse.o hfsfuse src/hfsdump.o hfsdump src/hfstar.o hfstar libhfsuser.pc $(DEPS)

distclean: clean
	$(RM) config.mak src/version.h AUTHORS "$(RELEASE_NAME).tar.gz"

install-lib: lib
	for dir in $(LIBDIRS); do $(MAKE) -C $$dir install; done
	[ -d $(DESTDIR)$(pkgconfigdir) ] && $(INSTALL) -m644 libhfsuser.pc $(DESTDIR)$(pkgconfigdir)

uninstall-lib:
	for dir in $(LIBDIRS); do $(MAKE) -C $$dir uninstall; done
	$(RM) $(DESTDIR)$(pkgconfigdir)/libhfsuser.pc

ifeq ($(OS), Haiku)
install: $(TARGETS)
	mkdir -pm755 $(DESTDIR)$(prefix)/add-ons/userlandfs/
	$(INSTALL) -m644 hfsfuse $(DESTDIR)$(prefix)/add-ons/userlandfs/
	$(INSTALL) -m755 hfsdump $(DESTDIR)$(bindir)
	[ -f hfstar ] && $(INSTALL) -m755 hfstar $(DESTDIR)$(bindir)

uninstall:
	$(RM) $(DESTDIR)$(prefix)/add-ons/userlandfs/hfsfuse $(DESTDIR)$(bindir)/hfsdump $(DESTDIR)$(bindir)/hfstar
else
install: $(TARGETS)
	mkdir -pm755 $(DESTDIR)$(bindir)
	$(INSTALL) -m755 $^ $(DESTDIR)$(bindir)

uninstall:
	$(RM) $(DESTDIR)$(bindir)/hfsfuse $(DESTDIR)$(bindir)/hfsdump $(DESTDIR)$(bindir)/hfstar
endif

version:
	echo \#define HFSFUSE_VERSION_STRING $(VERSION_STRING) > src/version.h

authors:
	git shortlog -sne $(RELEASE_BRANCH) | cut -f 2- > AUTHORS

showconfig:
	@echo "$$CONFIG"

config:
	@echo "$$CONFIG" > config.mak

dist: version authors
	git archive $(RELEASE_BRANCH) -o "$(RELEASE_NAME).tar.gz" --prefix "$(RELEASE_NAME)/src/" --add-file src/version.h --prefix "$(RELEASE_NAME)/" --add-file AUTHORS

-include $(DEPS)
