# Options:
#   BUILD_DIR:       directory into which all build files will be put
#   FWK_DIR:         relative path to libfwk (has to be specified)
#   PLATFORM:        linux, mingw or html
#   [FWK_]MODE:      release, devel or debug [-nans]
#   ADD_CFLAGS:      additional compiler flags
#   ADD_LDFLAGS:     additional linker flags
#   COMPILER:        name of the compiler; clang++ or g++ is used by default
#   LINKER:          name of linker; by default same as compiler
#   MINGW_PREFIX:    prefix for mingw toolset; Can be set in the environment
#   STATS:           gathers build times for each object file (STATS_CMD has to be used)
#
# Conditional compilation:
#   FWK_IMGUI:     enabled or disabled (by default if imgui is present, it's enabled)
#   FWK_GEOM:      enabled or disabled (enabled by default)
#   FWK_DWARF:     using libdwarf for backtrace analysis; enabled or disabled
#                  (enabled on linux by default if libdwarf is present)
#
# Following variables will be defined:
#   CFLAGS
#   LDFLAGS
#   FWK_BUILD_DIR
#   FWK_LIB_FILE
#   CONFIG_FILE
#   PROGRAM_SUFFIX
#   (and more)
#
# Build modes:
# - release: full optimization (-O3)
# - devel:   full optimization (-O3), paranoid mode enabled
# - debug:   minimal optimization (-Og), paranoid mode enabled
#
# Special targets:
#  print-variables: prints contents of main variables
#  print-stats:     prints build stats (generated with STATS option & STATS_CMD)
#  clean-libfwk:    clears libfwk
#  clean-all:       clears all possible targets (including checker)
#
# Developer options:
#   JUNK_GATHERING: Flag which may be passed when gathering info about junk files.
#                   When it's enabled you should disable statements which may slow down parsing
#                   and don't affect JUNK_* definitions (for example do not create sub-directories or
#                   not include .d files).
#
# Makefile-shared keeps track of flags passed to make and recreates PCH if they change.
# Tracked flags: ADD_CFLAGS, COMPILER and conditional compilation flags.

VALID_PLATFORMS  := linux mingw html
VALID_MODES      := debug debug-nans release release-nans devel devel-nans

PLATFORM         = linux
MODE            ?= devel
FWK_MODE        ?= $(MODE)
MINGW_PREFIX    ?= x86_64-w64-mingw32.static.posix-
LINKER           = $(COMPILER)
BASE_MODE       := $(if $(findstring release,$(MODE)),release,$(if $(findstring devel,$(MODE)),devel,debug))

COMPILER_linux  := clang++
COMPILER_mingw  := $(MINGW_PREFIX)g++
COMPILER_html   := emcc
COMPILER        := $(COMPILER_$(PLATFORM))

# clang, gcc or emcc
# make emcc (or html) platform, leave compiler as clang?
# TODO: emcc is clang!
COMPILER_TYPE   := $(if $(findstring clang,$(COMPILER)),clang,$(if $(findstring emcc,$(COMPILER)),emcc,gcc))

TOOLSET_PREFIX_mingw := $(MINGW_PREFIX)
TOOLSET_PREFIX_html  := em
TOOLSET_PREFIX       := $(TOOLSET_PREFIX_$(PLATFORM))

# TODO: some of these tools don't make sense on emcc
ARCHIVER             := $(TOOLSET_PREFIX)ar
STRIPPER             := $(TOOLSET_PREFIX)strip
PKG_CONFIG           := $(TOOLSET_PREFIX)pkg-config

PROGRAM_SUFFIX_mingw := .exe
PROGRAM_SUFFIX_html  := .html
PROGRAM_SUFFIX       := $(PROGRAM_SUFFIX_$(PLATFORM))

PLATFORM_PREFIX      := $(if $(findstring linux,$(PLATFORM)),,$(PLATFORM)_)
FWK_BUILD_DIR        := $(FWK_DIR)build/$(PLATFORM_PREFIX)$(FWK_MODE)
FWK_LIB_NAME         := fwk_$(PLATFORM_PREFIX)$(FWK_MODE)
FWK_LIB_FILE         := $(FWK_DIR)lib/lib$(FWK_LIB_NAME).a

export FWK_GEOM      ?= enabled
export FWK_IMGUI     := $(if $(wildcard $(FWK_DIR)extern/imgui/imgui.h),enabled,disabled)
ifeq ($(PLATFORM), linux)
export FWK_DWARF  := $(if $(wildcard /usr/include/libdwarf/libdwarf.h),enabled, disabled)
else
export FWK_DWARF  := disabled
endif

# --- Checking for errors -------------------------------------------------------------------------

ifeq ($(filter $(VALID_PLATFORMS),$(PLATFORM)),)
$(info Valid platforms: $(VALID_PLATFORMS))
$(error ERROR: invalid PLATFORM specified: $(PLATFORM))
endif

ifeq ($(filter $(VALID_MODES),$(MODE)),)
$(info Valid modes: $(VALID_MODES))
$(error ERROR: invalid MODE specified: $(MODE))
endif

ifndef FWK_DIR
($error ERROR: FWK_DIR undefined)
endif

# --- Compilation & linking flags -----------------------------------------------------------------

HTML_shared  := -s EXIT_RUNTIME=1 -s USE_SDL=2 -s USE_VORBIS=1 -s USE_FREETYPE=1 -s USE_WEBGL2=1 -s USE_ZLIB=1
LIBS_shared  += freetype2 sdl2 vorbisfile
LIBS_linux   += $(shell $(PKG_CONFIG) --libs $(LIBS_shared)) -lshaderc_combined -lz -lopenal -lvulkan -lrt -lm -lstdc++
LIBS_mingw   += $(shell $(PKG_CONFIG) --libs $(LIBS_shared)) -lz -lOpenAL32 -ldsound -lole32 -lwinmm \
                    -lglu32 -lvulkan -lws2_32 -limagehlp

ifeq ($(FWK_DWARF), enabled)
LIBS_linux   += -ldwarf -lelf -ldl
LIBS_mingw   += -ldwarf -lz
endif

# Clang gives no warnings for uninitialized class members!
#TODO:-Wno-strict-overflow
CFLAGS         += -I$(FWK_DIR)include/ -fno-exceptions -funwind-tables -std=c++2a \
                  -Wall -Wextra -Woverloaded-virtual -Wnon-virtual-dtor -Wno-reorder -Wuninitialized \
                  -Wno-unused-function -Werror=switch -Werror=delete-incomplete -Wno-unused-variable \
				  -Wno-unused-parameter -Wparentheses -Wno-overloaded-virtual

CFLAGS_clang   += -Wconstant-conversion -Werror=return-type -Wno-undefined-inline
ifneq ("$(wildcard $(FWK_DIR)checker.so)","")
CFLAGS_clang   += -Xclang -load -Xclang "$(realpath $(FWK_DIR)checker.so)" \
                  -Xclang -add-plugin -Xclang fwk-check-exceptions
                 #-Xclang -add-plugin -Xclang fwk-print-type-aliases
endif
CFLAGS_gcc     += -Werror=aggressive-loop-optimizations -Wno-unused-but-set-variable -Wno-stringop-overflow
CFLAGS_emcc    += -Wno-ambiguous-reversed-operator

CFLAGS_release += -O3 $(if $(findstring html,$(PLATFORM)),,-g2)
CFLAGS_devel   += -DFWK_PARANOID -O3 -g2
CFLAGS_debug   += -DFWK_PARANOID $(if $(findstring html,$(PLATFORM)),-O1 -g2,-Og -g3)

CFLAGS_linux   += -pthread
CFLAGS_mingw   += -pthread -msse2 -mfpmath=sse
CFLAGS_emcc    += $(HTML_shared)

CFLAGS_opts    := $(if $(findstring enabled,$(FWK_GEOM)),,-DFWK_GEOM_DISABLED) \
                  $(if $(findstring enabled,$(FWK_IMGUI)),,-DFWK_IMGUI_DISABLED) \
                  $(if $(findstring enabled,$(FWK_DWARF)),,-DFWK_DWARF_DISABLED) \
                  $(if $(findstring nans,$(MODE)),-DFWK_CHECK_NANS,)
CFLAGS         += $(strip $(CFLAGS_opts) $(CFLAGS_$(BASE_MODE)) $(CFLAGS_$(PLATFORM)) $(CFLAGS_$(COMPILER_TYPE)) $(ADD_CFLAGS))

LDFLAGS_clang  += -fuse-ld=gold
LDFLAGS_linux  += -pthread
LDFLAGS_mingw  += -pthread
LDFLAGS_emcc   += $(HTML_shared)
LDFLAGS        += $(LDFLAGS_$(PLATFORM)) $(LDFLAGS_$(COMPILER_TYPE)) $(LIBS_$(PLATFORM)) $(ADD_LDFLAGS)

# Creating build dir if necessary
_dummy := $(shell mkdir -p $(BUILD_DIR))

# --- Updating configuration file -----------------------------------------------------------------

ifdef BUILD_DIR

CONFIG_FILE    := $(BUILD_DIR)/make_config.txt

CONFIG_STRING  := COMPILER=$(COMPILER):ADD_CFLAGS=$(ADD_CFLAGS):FWK_GEOM=$(FWK_GEOM):FWK_IMGUI=$(FWK_IMGUI):FWK_DWARF=$(FWK_DWARF)
ifneq ($(wildcard $(CONFIG_FILE)),)
CONFIG_CURRENT := $(shell cat $(CONFIG_FILE))
endif

$(CONFIG_FILE):
	$(file > $(CONFIG_FILE),$(CONFIG_STRING))

ifneq ($(CONFIG_CURRENT),$(CONFIG_STRING))
$(CONFIG_FILE): .FORCE
endif

endif

# --- Precompiled headers configuration -----------------------------------------------------------

#  PCH_SOURCE and BUILD_DIR has to be defined for precompiled headers to work
#
#  Defined variables:
#  PCH_TARGET:    path to precompiled header file
#  PCH_TEMP:      path to temporary file created in BUILD_DIR (add it to your deps with .d suffix)
#  PCH_CFLAGS:     compiler commands which enable PCH

ifdef PCH_SOURCE
ifdef BUILD_DIR

PCH_TEMP           := $(BUILD_DIR)/$(basename $(notdir $(PCH_SOURCE)))_.h
PCH_CLANG_TARGET   := $(PCH_TEMP).pch
PCH_GCC_TARGET     := $(PCH_TEMP).gch
PCH_JUNK_FILES     := $(CONFIG_FILE) $(PCH_CLANG_TARGET) $(PCH_GCC_TARGET) $(PCH_TEMP) $(PCH_TEMP).d

ifeq ($(COMPILER_TYPE),clang)
PCH_CFLAGS        := -include-pch $(PCH_CLANG_TARGET)
PCH_TARGET        := $(PCH_CLANG_TARGET)
else
PCH_CFLAGS        := -I$(BUILD_DIR) -include $(PCH_TEMP)
PCH_TARGET        := $(PCH_GCC_TARGET)
endif

$(PCH_TEMP): $(PCH_SOURCE)
	cp $^ $@
$(PCH_TARGET): $(PCH_TEMP) $(CONFIG_FILE)
	$(COMPILER) -x c++-header -MMD $(CFLAGS) $(PCH_TEMP) -o $@

endif
endif

# --- Helper functions & other targets ------------------------------------------------------------

# Filters existing files from given file list
# Arguments: subdir_list, file_list
# Also allows files existing in current directory
filter-existing = $(filter $(shell find $(1) -type f) $(shell ls),$(2))

# Stats gathering:
# STATS_CMD:  command which has to prefix all build command you wish to measure
# STATS_FILE: file into which statistics will be gathered

STATS_FILE = $(BUILD_DIR)/stats.txt
ifdef STATS
STATS_CMD  = time -o $(STATS_FILE) -a -f "%U $*.o"
endif

print-stats:
	sort -n -r $(STATS_FILE)

print-variables:
	@echo "PLATFORM      = $(PLATFORM)"
	@echo "MODE          = $(MODE)"
	@echo "BASE_MODE     = $(BASE_MODE)"
	@echo "COMPILER      = $(COMPILER)"
	@echo "LINKER        = $(LINKER)"
	@echo "COMPILER_TYPE = $(COMPILER_TYPE)"
	@echo
	@echo "FWK_BUILD_DIR = $(FWK_BUILD_DIR)"
	@echo "FWK_LIB_FILE  = $(FWK_LIB_FILE)"
	@echo 
	@echo "PCH_TARGET = $(PCH_TARGET)"
	@echo "PCH_CFLAGS = $(PCH_CFLAGS)"
	@echo 
	@echo "FWK_IMGUI     = $(FWK_IMGUI)"
	@echo "FWK_GEOM      = $(FWK_GEOM)"
	@echo 
	@echo "CFLAGS  = $(CFLAGS)"
	@echo 
	@echo "LDFLAGS = $(LDFLAGS)"

# --- Build targets for libfwk users --------------------------------------------------------------

ifneq ($(FWK_DIR),)
FWK_MAKE_ARGS  = -C $(FWK_DIR) PLATFORM=$(PLATFORM) MODE=$(FWK_MODE)

$(FWK_LIB_FILE): .FORCE
	$(MAKE) $(FWK_MAKE_ARGS) lib/lib$(FWK_LIB_NAME).a
endif

# --- Clean targets  ------------------------------------------------------------------------------

# JUNK_DIRS and JUNK_FILES have to be defined.
# Only JUNK_FILES within specified JUNK_DIRS will be deleted.
#
# Defined variables: EXISTING_JUNK, ALL_JUNK (for all targets & platforms)
# Defined targets: clean, clean-all, clean-libfwk

EXISTING_JUNK = $(call filter-existing,$(JUNK_DIRS),$(JUNK_FILES) $(PCH_JUNK_FILES) $(STATS_FILE))

print-junk-files:
	@echo $(EXISTING_JUNK)

#This macro will only be evaluated when clean-all target is used
ALL_EXISTING_JUNK = $(sort $(shell \
	for platform in $(VALID_PLATFORMS) ; do for mode in $(VALID_MODES) ; do \
		$(MAKE) PLATFORM=$$platform MODE=$$mode print-junk-files JUNK_GATHERING=1 -s ; \
	done ; done))

clean:
	-rm -f $(sort $(EXISTING_JUNK))
	find $(JUNK_DIRS) -type d -empty -delete

clean-all:
	-rm -f $(sort $(ALL_EXISTING_JUNK))
	find $(JUNK_DIRS) -type d -empty -delete
ifneq ($(FWK_DIR),)
	$(MAKE) $(FWK_MAKE_ARGS) clean-all

clean-libfwk:
	$(MAKE) $(FWK_MAKE_ARGS) clean-all
.PHONY: clean-libfwk
endif

.PHONY: clean clean-all print-junk-files print-variables print-stats

.FORCE:
