##########
# COMMANDS

DOCKER:=docker
DOCKER_COMPOSE:=docker-compose
docker_bash=$(1)

# Check if needs root privileges?
PRIVGROUP:=docker
ifneq ($(findstring $(PRIVGROUP),$(shell groups)),$(PRIVGROUP))
DOCKER:=sudo $(DOCKER)
docker_bash=sudo bash -c '$(1)'
endif

#########
# CONFIGS

CONF_WILDC:=$(wildcard $(PWD)/*.conf)
# apply source to *all* configs!
CONF_SOURCE:=$(patsubst %,. %;,$(CONF_WILDC))

# extraction of env variables from *.conf files
confvalue=$(shell $(CONF_SOURCE) echo -n $${$(1)})

# docker network name
CONF_KIWI_HUB_NAME:=$(call confvalue,KIWI_HUB_NAME)
ifeq ($(CONF_KIWI_HUB_NAME),)
$(error KIWI_HUB_NAME not set in $(CONF_WILDC))
endif

# docker network CIDR 
CONF_KIWI_HUB_CIDR:=$(call confvalue,KIWI_HUB_CIDR)
ifeq ($(CONF_KIWI_HUB_CIDR),)
$(error KIWI_HUB_CIDR not set in $(CONF_WILDC))
endif

# persistent data directory
CONF_TARGET_ROOT:=$(call confvalue,TARGET_ROOT)
ifeq ($(CONF_TARGET_ROOT),)
$(error TARGET_ROOT not set in $(CONF_WILDC))
endif

# suffix for project directories
PROJ_SUFFX:=$(call confvalue,SUFFIX_PROJECT)
ifeq ($(PROJ_SUFFX),)
$(error SUFFIX_PROJECT not set in $(CONF_WILDC))
endif

# suffix for disabled project directories
DOWN_SUFFX:=$(call confvalue,SUFFIX_DOWN)
ifeq ($(DOWN_SUFFX),)
$(error SUFFIX_DOWN not set in $(CONF_WILDC))
endif

#########
# CONSTANTS

# file to store docker network cidr
KIWI_HUB_FILE:=$(CONF_TARGET_ROOT)/up-$(CONF_KIWI_HUB_NAME)

# remove any suffix $2 from $1
rmsuffix=$(patsubst %$2,%,$1)
# remove project suffix from $1
projname=$(call rmsuffix,$1,$(PROJ_SUFFX))

# project directory handling
PROJ_WILDC:=$(wildcard *$(PROJ_SUFFX))
PROJ_NAMES:=$(call projname,$(PROJ_WILDC))

#########
# FUNCTIONS

# run DOCKER_COMPOSE:
#  - in project directory
#  - with sourced *.conf files
#  - with COMPOSE_PROJECT_NAME, CONFDIR and TARGETDIR set
kiwicompose=$(call docker_bash,\
	cd "$<"; \
	$(CONF_SOURCE) \
	COMPOSE_PROJECT_NAME="$(call projname,$<)" \
	CONFDIR="$(CONF_TARGET_ROOT)/conf" \
	TARGETDIR="$(CONF_TARGET_ROOT)/$<" \
	$(DOCKER_COMPOSE) $(1))

#########
# TARGETS

# default target
.PHONY: all
all: purge-conf up

#########
# manage the docker network (container name local DNS)
$(KIWI_HUB_FILE):
	-$(DOCKER) network create \
		--driver bridge \
		--internal \
		--subnet "$(CONF_KIWI_HUB_CIDR)" \
		"$(CONF_KIWI_HUB_NAME)"
	@echo "Creating canary $(KIWI_HUB_FILE) ..."
	@$(DOCKER) run --rm \
		-v "/:/mnt" -u root alpine:latest \
		ash -c '\
			mkdir -p "$(addprefix /mnt, $(CONF_TARGET_ROOT))"; \
			echo "$(CONF_KIWI_HUB_CIDR)" > "$(addprefix /mnt, $(KIWI_HUB_FILE))"; \
		'

.PHONY: net-up
net-up: $(KIWI_HUB_FILE)

.PHONY: net-down
net-down: down
	$(DOCKER) network rm "$(CONF_KIWI_HUB_NAME)"
	@echo "Removing canary $(KIWI_HUB_FILE) ..."
	@$(DOCKER) run --rm \
		-v "/:/mnt" -u root alpine:latest \
		ash -c '\
			rm -f "$(addprefix /mnt, $(KIWI_HUB_FILE))"; \
		'

#########
# sync project config directory to variable folder

# Dockerfile for running rsync as root
define DOCKERFILE_RSYNC
FROM alpine:latest
RUN  apk --no-cache add rsync
endef

.PHONY: copy-conf
copy-conf:
ifneq ($(wildcard *${PROJ_SUFFX}/conf),)
	$(eval export DOCKERFILE_RSYNC)
	@echo "Building auxiliary image ldericher/kiwi-config:rsync ..."
	@echo -e "$${DOCKERFILE_RSYNC}" | \
		$(DOCKER) build -t ldericher/kiwi-config:rsync . -f- &> /dev/null

	$(eval sources:=$(wildcard *${PROJ_SUFFX}/conf))
	@echo "Syncing $(sources) to $(CONF_TARGET_ROOT) ..."

	$(eval sources:=$(realpath $(sources)))
	$(eval sources:=$(addprefix /mnt, $(sources)))
	$(eval sources:=$(patsubst %,'%',$(sources)))
	$(eval dest:='$(addprefix /mnt, $(CONF_TARGET_ROOT))')

	@$(DOCKER) run --rm \
		-v "/:/mnt" -u root ldericher/kiwi-config:rsync \
		ash -c '\
			rsync -r $(sources) $(dest); \
		'
endif

.PHONY: purge-conf
purge-conf:
	@echo "Emptying $(CONF_TARGET_ROOT)/conf ..."
	@$(DOCKER) run --rm \
		-v "/:/mnt" -u root alpine:latest \
		ash -c '\
			rm -rf "$(addprefix /mnt, $(CONF_TARGET_ROOT)/conf)"; \
		'

#########
# manage all projects
.PHONY: up down update
up: net-up copy-conf $(patsubst %,%-up,$(PROJ_NAMES))
down: $(patsubst %,%-down,$(PROJ_NAMES))
update: $(patsubst %,%-update,$(PROJ_NAMES))

#########
# manage single project
.PHONY: %-up
%-up: %$(PROJ_SUFFX) net-up
	$(call kiwicompose,up -d $(x))

.PHONY: %-down
ifeq ($(x),)
%-down: %$(PROJ_SUFFX)
	$(call kiwicompose,down)
else
%-down: %$(PROJ_SUFFX)
	$(call kiwicompose,stop $(x))
	$(call kiwicompose,rm -f $(x))
endif

.PHONY: %-pull
%-pull: %$(PROJ_SUFFX)
	$(call kiwicompose,pull --ignore-pull-failures $(x))

.PHONY: %-build
%-build: %$(PROJ_SUFFX)
	$(call kiwicompose,build --pull $(x))

.PHONY: %-logs
%-logs: %$(PROJ_SUFFX)
	$(call kiwicompose,logs -t $(x)) 2>/dev/null | less -R +G

.PHONY: %-logf
%-logf: %$(PROJ_SUFFX)
	$(call kiwicompose,logs -tf --tail=10 $(x)) ||:

ifneq ($(x),)
s?=bash
.PHONY: %-sh
%-sh: %$(PROJ_SUFFX)
	$(call kiwicompose,exec $(x) /bin/sh -c "[ -e /bin/$(s) ] && /bin/$(s) || /bin/sh")
endif

# enabling and disabling
.PHONY: %-enable %-disable
%-enable: %$(PROJ_SUFFX)$(DOWN_SUFFX)
	mv "$<" "$(call projname,$(call rmsuffix,$<,$(DOWN_SUFFX)))$(PROJ_SUFFX)"
%-disable: %$(PROJ_SUFFX)
	mv "$<" "$<$(DOWN_SUFFX)"

# Combinations
.PHONY: %-update
%-update: %$(PROJ_SUFFX) %-build %-pull copy-conf
	$(MAKE) $(call projname,$<)-down
	$(MAKE) $(call projname,$<)-up

# Arbitrary compose command
.PHONY: %-cmd
%-cmd: %$(PROJ_SUFFX)
	$(call kiwicompose,$(x))

#########
# project creation
.PHONY: %-new
%-new:
	$(eval proj_dir:=$(patsubst %-new,%$(PROJ_SUFFX)$(DOWN_SUFFX),$@))
	mkdir $(proj_dir)
	$(eval export COMPOSEFILE)
	echo -e "$${COMPOSEFILE}" > $(proj_dir)/docker-compose.yml

# default compose file
define COMPOSEFILE
version: "2"

networks:
  # reachable from outside
  default:
    driver: bridge
  # interconnects projects
  kiwi_hub:
    external:
      name: $$KIWI_HUB_NAME

services:
  something:
    image: maintainer/repo:tag
    restart: unless-stopped
    networks:
      - default
      - kiwi_hub
    [...]
endef