SHELL=bash
# MAKE=make -d

export CONTAINER_COMMAND ?= podman
export CONTAINER_BGRUN ?= $(CONTAINER_COMMAND) run -dit --network=host
export CONTAINER_FGRUN ?= $(CONTAINER_COMMAND) run -it --network=host

export IN_CONTAINER ?= false
export IN_CI ?= false

export WATCH
export UNIWORXDB_OPTS ?= -cf

export CONTAINER_ID
export FRADRIVE_SERVICE

export DEV_PORT_HTTP
export DEV_PORT_HTTPS
export HOOGLE_PORT

export IMAGE_REGISTRY = docker.io
export MEMCACHED_IMAGE = $(IMAGE_REGISTRY)/memcached:latest
export MINIO_IMAGE = $(IMAGE_REGISTRY)/minio/minio:latest
export MAILDEV_IMAGE = $(IMAGE_REGISTRY)/maildev/maildev:latest # TODO: needs different port than 1025 to avoid conflicts

export DEVELOP
export CONTAINER_FILE
export CONTAINER_FILE_CONTENT

export PROD ?= false
export LOGSIZE ?= 1024

# HELP HEADER START
# To see the definition of all available targets, take a look into the Makefile.
# Targets starting with '--' are not meant to be directly called.
# If you want to do so anyway please use 'make -- [--target]' to avoid
# '[--target]' being treated as option to 'make'.
# 
# 
# Targets meant to be used by humans are:
# HELP HEADER END

###########################
##### GENERAL TARGETS #####

.PHONY: help
# HELP: print out this help message
help:
	@if [ -z "$$(which perl 2>/dev/null)" ] ; then \
	  $(CONTAINER_FGRUN) .:/mnt 'debian:12.5' '/mnt/utils/makehelp.pl' '/mnt/Makefile' ; \
	else \
	utils/makehelp.pl Makefile ; \
	fi

.PHONY: clean
# HELP: remove all compilation results in the directory but leave containers and images unharmed
clean:
	-rm -rf node_modules .npm .cache assets/icons assets/favicons static well-known
	-rm -rf .stack-work .stack-work-build .stack-work-run .stack-work-test .stack-work-doc
	-rm -rf bin .Dockerfile develop
.PHONY: clean-all
# HELP: like clean but with container and image prune
clean-all: clean
	-rm -rf .stack
	-$(CONTAINER_COMMAND) system prune --all --force --volumes
	-$(CONTAINER_COMMAND) image prune --all --force
	-$(CONTAINER_COMMAND) volume prune --force

.PHONY: release
# HELP: create, commit and push a new release
release:
	./.gitlab-ci/version.pl -changelog CHANGELOG.md
	git add CHANGELOG.md
	VERSION=`.gitlab-ci/version.pl`
	git tag $${VERSION}
	git commit -m "chore(release): $${VERSION}"
	git push

# TODO: rewrite shell targets to enter shell inside currently running containers, do not just launch new containers!
.PHONY: %-shell
# HELP: launches bash-shell inside backend/frontend container
%-shell: ENTRYPOINT=/bin/bash
%-shell: --%-shell;
--%-shell:
	MOUNT_DIR=/mnt/fradrive ; \
	FRADRIVE_SERVICE=$* ; \
	$(MAKE) -- --image-build FRADRIVE_SERVICE=$${FRADRIVE_SERVICE} ; \
	$(CONTAINER_FGRUN) -v $(PWD):$${MOUNT_DIR} --env IN_CONTAINER=true --env CONTAINER_FILE=$(CONTAINER_FILE) --entrypoint $(ENTRYPOINT) --name fradrive.$${FRADRIVE_SERVICE}.interactive.$$(date +'%Y-%m-%dT%H-%M-%S') fradrive/$${FRADRIVE_SERVICE}

##### GENERAL TARGETS #####
###########################


############################################
##### UNIFIED FRONTEND/BACKEND TARGETS #####

.PHONY: start
# HELP: start database services, frontend and backend
start: new-develop start-database start-memcached start-minio start-frontend start-backend;

.PHONY: compile
# HELP: compile frontend and backend
compile: compile-frontend compile-backend;

.PHONY: lint
# HELP: lint frontend and backend
lint: lint-frontend lint-backend;

.PHONY: test
# HELP: test frontend, backend, and check internationalization
test: test-frontend test-backend i18n-check;

##### UNIFIED FRONTEND/BACKEND TARGETS #####
############################################


############################
##### FRONTEND TARGETS #####

.PHONY: %-frontend
%-frontend: FRADRIVE_SERVICE=frontend
%-frontend: --image-build --containerized---%-frontend;

--%-frontend: --containerized-static-frontend --containerized-well-known-frontend;

# HELP(compile-frontend): compile frontend
--compile-frontend: static well-known;

.PHONY: serve-frontend
# HELP: serve frontend (watch file changes)
serve-frontend:
	$(MAKE) -- --containerized---compile-frontend WATCH=--watch

# --serve-frontend: WATCH=--watch
# --serve-frontend: --compile-frontend;

# HELP(lint-frontend): lint frontend
--lint-frontend: eslint.config.js
	npx -- eslint frontend/src $(FIX)
	@echo Hooray! There are no hints.

# HELP(test-frontend): test frontend
--test-frontend: karma.conf.cjs
	@echo Karma frontend tests are currently broken after npm update and have therefor been temporarily disabled.
# npx -- karma start --conf karma.conf.cjs $(WATCH)

node_modules: package.json package-lock.json
	npm ci --cache .npm --prefer-offline

package-lock.json: package.json
	npm install --cache .npm --prefer-offline

assets: assets/favicons assets/icons;
assets/favicons:
	./utils/faviconize.pl assets/favicon.svg long assets/favicons
assets/icons: node_modules
	./utils/renamer.pl node_modules/@fortawesome/fontawesome-free/svgs/solid utils/rename-fa.json assets/icons/fradrive
	./utils/renamer.pl node_modules/@fortawesome/fontawesome-free/svgs/regular utils/rename-fa.json assets/icons/fradrive

static: node_modules esbuild.config.mjs assets
	npm run build
well-known: static;

##### FRONTEND TARGETS #####
############################


###########################
##### BACKEND TARGETS #####

.PHONY: %-backend
%-backend: FRADRIVE_SERVICE=backend
%-backend: --image-build --containerized---%-backend;

# TODO: to be deprecated by start-%
.PHONY: serve-backend
# HELP: serve backend
serve-backend:
	DEV_PORT_HTTP=`utils/next_free_port.pl 3000 | tee .dev-port-http`; \
	DEV_PORT_HTTPS=`utils/next_free_port.pl 3443 | tee .dev-port-https`; \
	$(MAKE) -- --containerized---serve-dev-backend DEV_PORT_HTTP=$${DEV_PORT_HTTP} DEV_PORT_HTTPS=$${DEV_PORT_HTTPS}
#	DEV_PORT_HTTP=`grep 'https:' develop/$(DATE)/backend/$(CONTAINER_ID) | sed 's/.*://'`; \
#	DEV_PORT_HTTPS=`grep 'https:' develop/$(DATE)/backend/$(CONTAINER_ID) | sed 's/.*://'`; \
#	DEVELOP=$(MAKE) develop/
--serve-dev-backend: start.sh
	DEV_PORT_HTTP=`cat .dev-port-http`; \
	DEV_PORT_HTTPS=`cat .dev-port-https`; \
	./start.sh
#	./utils/watchrun.sh develop/ ./start.sh

# HELP(compile-backend): compile backend
--compile-backend:
	stack build --fast --work-dir .stack-work-build --profile --library-profiling --executable-profiling --flag uniworx:-library-only --local-bin-path $$(pwd)/bin $(stackopts)

# HELP(lint-backend): lint backend
--lint-backend:
	stack build --test --fast --work-dir .stack-work-test --flag uniworx:library-only uniworx:test:hlint $(stackopts)

# HELP(test-backend): test backend
--test-backend:
	stack build --test --coverage --fast --workdir .stack-work-test --flag uniworx:library-only $(stackopts)

# TODO: better name
.PHONY: db
# HELP: clear and fill database. requires running postgres
db: FRADRIVE_SERVICE=backend
db: --image-build --containerized---db;
# TODO (db-m-$MIGRATION-backend): apply migration (see src/Model/Migration/Definition.hs for list of available migrations)
--db-backend: .stack
	SERVER_SESSION_ACID_FALLBACK=${SERVER_SESSION_ACID_FALLBACK:-true} ; \
	AVSPASS=${AVSPASS:-nopasswordset} ; \
	stack exec uniworxdb -- $(UNIWORXDB_OPTS)

.PHONY: .stack
.stack:
	if [ "$(IN_CONTAINER)" == "true" ] ; then \
	  $(MAKE) -- --.stack ; \
	else \
	  $(MAKE) -- --image-run---.stack ; \
	fi
--.stack: stack.yaml stack.yaml.lock package.yaml
	stack build --fast --only-dependencies $(stackopts)

##### BACKEND TARGETS #####
###########################


############################
##### DATABASE TARGETS #####

# TODO: to be deprecated by start-%
.PHONY: serve-database
# HELP: serve database
serve-database: --containerized-database;

--containerized-database: FRADRIVE_SERVICE=database
# port forwarding is disabled in --network=host mode; nevertheless it is stated here for documentation reasons
--containerized-database: docker/database/initdb.sh docker/database/pg_hba.conf docker/database/postgresql.conf docker/database/schema.sql --image-build
	if [ "$(IN_CONTAINER)" == "false" ] ; then \
	  $(CONTAINER_BGRUN) --name fradrive.$(FRADRIVE_SERVICE).$$(date +'%Y-%m-%dT%H-%M-%S') fradrive/$(FRADRIVE_SERVICE) ; \
	fi

##### DATABASE TARGETS #####
############################


#############################
##### CONTAINER TARGETS #####

--containerized-%-frontend: FRADRIVE_SERVICE=frontend
--containerized-%-frontend: --image-build
	$(MAKE) -- --image-run-$*-frontend

--containerized-%-backend: FRADRIVE_SERVICE=backend
--containerized-%-backend: --image-build
	$(MAKE) -- --image-run-$*-backend 
--containerized-%-hoogle: --containerized-%-hoogle-backend;
--containerized---db: --containerized-db-backend;

# --containerized-%-minio: FRADRIVE_SERVICE=minio
# --containerized-%-minio: --image-build
# 	$(MAKE) -- --image-run-$*-minio
--containerized---start-minio:
	MINIO_DIR=`mktemp` ; \
	./utils/watchcontainerrun.sh "$(CONTAINER_COMMAND)" "$(CONTAINER_FILE)" "" "rm -rf $${MINIO_DIR}" & \
	CONTAINER_ID=`$(CONTAINER_BGRUN) --name fradrive.minio.$$(date +'%Y-%m-%dT%H-%M-%S') $(MINIO_IMAGE) -- server $${MINIO_DIR}` ; \
	echo "CONTAINER_ID=$${CONTAINER_ID}" >> $(CONTAINER_FILE)

# --containerized-%-memcached: FRADRIVE_SERVICE=memcached
# --containerized-%-memcached: --image-build
# 	$(MAKE) -- --image-run-$*-memcached
--containerized---start-memcached:
	./utils/watchcontainerrun.sh "$(CONTAINER_COMMAND)" "$(CONTAINER_FILE)" &
	CONTAINER_ID=`$(CONTAINER_BGRUN) $(MEMCACHED_IMAGE)` ; \
	echo "CONTAINER_ID=$${CONTAINER_ID}" >> $(CONTAINER_FILE)

--containerized-%-database: FRADRIVE_SERVICE=database
--containerized-%-database: --image-build
	$(MAKE) -- --image-run-$*-database

.PHONY: image-rebuild
# HELP(image-rebuild-{backend,frontend,database,memcached,minio}): force-rebuild the stated docker image
image-rebuild-%:
	$(MAKE) -- --image-build FRADRIVE_SERVICE=$* NO_CACHE=--no-cache
--image-build:
	rm -f .Dockerfile
	ln -s docker/$(FRADRIVE_SERVICE)/Dockerfile .Dockerfile
	MOUNT_DIR=/mnt/fradrive; \
	PROJECT_DIR=/mnt/fradrive; \
	if [ "$(IN_CI)" == "true" ] ; then \
	  PROJECT_DIR=/fradrive; \
	fi; \
	if [ "$(IN_CONTAINER)" == "false" ] ; then \
	  $(CONTAINER_COMMAND) build $(NO_CACHE) -v $(PWD):$${MOUNT_DIR} --env IN_CONTAINER=true --build-arg MOUNT_DIR=$${MOUNT_DIR} --build-arg PROJECT_DIR=$${PROJECT_DIR} --tag fradrive/$(FRADRIVE_SERVICE) --file $(PWD)/.Dockerfile ; \
	fi

--image-run-%: docker/$(FRADRIVE_SERVICE)/Dockerfile
	MOUNT_DIR=/mnt/fradrive; \
	if [ "$(IN_CONTAINER)" == "true" ] ; then \
	  $(MAKE) -- $* ; \
	else \
	  if [ -z "$(CONTAINER_FILE)" ] ; then \
	    $(CONTAINER_FGRUN) -v $(PWD):$${MOUNT_DIR} --env IN_CONTAINER=true --env FRADRIVE_MAKE_TARGET=$* --env CONTAINER_FILE=$(CONTAINER_FILE) --env CONTAINER_FILE_CONTENT=$(CONTAINER_FILE_CONTENT) --env WATCH=$(WATCH) --name fradrive.$(FRADRIVE_SERVICE).$$(date +'%Y-%m-%dT%H-%M-%S') localhost/fradrive/$(FRADRIVE_SERVICE) ; \
	  else \
	    CONTAINER_ID=`$(CONTAINER_BGRUN) -v $(PWD):$${MOUNT_DIR} --env IN_CONTAINER=true --env FRADRIVE_MAKE_TARGET=$* --env CONTAINER_FILE=$(CONTAINER_FILE) --env CONTAINER_FILE_CONTENT=$(CONTAINER_FILE_CONTENT) --env WATCH=$(WATCH) --name fradrive.$(FRADRIVE_SERVICE).$$(date +'%Y-%m-%dT%H-%M-%S') localhost/fradrive/$(FRADRIVE_SERVICE)` ; \
	    echo "CONTAINER_ID=$${CONTAINER_ID}" >> "$(CONTAINER_FILE)"; \
	  fi \
	fi

# .PHONY: new-backend
# new-backend:
# 	LAST_DATE=`ls -1 develop | tail -n1`
# 	if [[ -z "$${LAST_DATE}" || -e `ls develop/$${LAST_DATE}/database` ]]; then \
#	  mkdir -p develop/$$(date +'%Y-%m-%dT%H-%M-%S') \
#	fi
#	serve-backend

#new-database:
#	mkdir -p develop/$$(date +'%Y-%m-%dT%H-%M-%S')
#	$(MAKE) serve-database

# TODO: work in progress
# - compute used ports and provide as env vars
# - start container in detached mode
# - save container id with ports as content as file
# .PHONY: serve-%
# serve-backend:
#	DEV_PORT_HTTP=`docker/backend/dev_port.pl 3000 | tee develop/$${DATE}/fradrive.backend.$${DATE}`; \
#	DEV_PORT_HTTPS=`docker/backend/dev_port.pl 3443 | tee .dev-port-https`; \
#	$(MAKE) -- --containerized---start-dev-backend DEV_PORT_HTTP=$${DEV_PORT_HTTP} DEV_PORT_HTTPS=$${DEV_PORT_HTTPS}
#--serve-dev-backend: start.sh
#	DEV_PORT_HTTP=`grep 'https:' develop/$(DATE)/backend/$(CONTAINER_ID) | sed 's/.*://'`; \
#	DEV_PORT_HTTPS=`grep 'https:' develop/$(DATE)/backend/$(CONTAINER_ID) | sed 's/.*://'`; \
#	./utils/watchrun.sh develop/ ./start.sh

DATE    := $(shell date +'%Y-%m-%dT%H-%M-%S')

SET_DEVELOP = $(eval DEVELOP=develop/`ls -1 develop | tail -n1`)
NEW_DEVELOP = $(eval DEVELOP=develop/$$(DATE))

.PHONY: new-develop
new-develop:
	$(NEW_DEVELOP)
	mkdir -p $(DEVELOP)
	$(MAKE) develop/.current
.PHONY: --develop
--develop:
	if ! [[ -e develop ]]; then \
	  $(MAKE) new-develop; \
	fi
	$(MAKE) develop/.current
.PHONY: develop/.current
develop/.current:
	$(SET_DEVELOP)
	echo "$(DEVELOP)" > develop/.current

start-database:  BASE_PORTS = "PGPORT=5432"
start-database:  SINGLETON = true

start-memcached: BASE_PORTS = "MEMCACHED_PORT=11211"
start-memcached: SINGLETON = true

start-minio:     BASE_PORTS = "UPLOAD_S3_PORT=9000"
start-minio:     SINGLETON = true

start-backend:   BASE_PORTS = "DEV_PORT_HTTP=3000" "DEV_PORT_HTTPS=3443"
start-backend:   SINGLETON = false
start-backend:   static

start-hoogle:    BASE_PORTS = "HOOGLE_PORT=8081"
start-hoogle:    SINGLETON = true

start-frontend:  SINGLETON = false
start-frontend:  WATCH = false

# HELP(start-database): start postgres server
# HELP(start-memcached): start memcached server
# HELP(start-minio): start minio server
# HELP(start-backend): serve yesod development site
# HELP(start-frontend): start frontend watcher. Watches frontend source files for changes and recompiles on change. (TODO: watcher functionality currently broken!)
# HELP(start-hoogle): serve local hoogle instance
start-%: FRADRIVE_SERVICE = %
start-%: PORTS = $(foreach PORT,$(BASE_PORTS),$(shell utils/next_free_port.pl $(PORT)))
start-%: --develop
	echo "$*"
	echo "$(DATE)"
	echo "DEV $(DEVELOP)"
	if [[ "$(SINGLETON)" = "true" ]]; then \
	  CONTAINER_FILE=$(DEVELOP)/$* ; \
	  if ! [[ -e $${CONTAINER_FILE} ]]; then \
	    >&2 echo "Another $* service is already running! Use \"make new-develop\" or \"make start\" to start a new develop instance despite currently running services." ; \
		exit 1 ; \
	  fi \
	else \
	  DEVDIR=$(DEVELOP)/$*; \
	  I=`ls $${DEVDIR} | sort -n | tail -n1`; \
	  echo "$${I}"; \
	  CONTAINER_FILE=$${DEVDIR}-$$(($${I}+1)); \
	fi ; \
	echo "$(PORTS)" | sed 's/ /\n/g' > $${CONTAINER_FILE} ; \
	$(MAKE) -- --containerized---start-$* CONTAINER_FILE=$${CONTAINER_FILE}

--start-backend: start.sh
	DEV_PORT_HTTP=`cat $(CONTAINER_FILE) | grep 'DEV_PORT_HTTP=' | sed 's/DEV_PORT_HTTP=//'`; \
	DEV_PORT_HTTPS=`cat $(CONTAINER_FILE) | grep 'DEV_PORT_HTTPS=' | sed 's/DEV_PORT_HTTPS=//'`; \
	./start.sh
--start-hoogle-backend:
	HOOGLE_PORT=`cat $(CONTAINER_FILE) | grep 'HOOGLE_PORT=' | sed 's/HOOGLE_PORT=//'` ; \
	echo "${HOOGLE_PORT}" ; \
	stack --work-dir .stack-work-doc hoogle -- -server --local --port $${HOOGLE_PORT}

--start-frontend: static
	npm run start

# --start-database: docker/database/initdb.sh docker/database/pg_hba.conf docker/database/postgresql.conf docker/database/schema.sql;
#	./utils/watchrun.sh "$(CONTAINER_FILE)" 

# TODO: mkdir inside develop/$(DATE)/minio instead of mktemp
# TODO: remove dir in stop-minio
#--containerized---start-minio:
#	MINIO_DIR=`mktemp` ; \
#	echo "MINIO_DIR=$${MINIO_DIR}" > $(DEVELOP)/minio ; \
#	CONTAINER_ID=`$(CONTAINER_RUN) -v $(PWD):/mnt/fradrive --file docker/minio/Dockerfile -- (server $${MINIO_DIR})` ; \
#	echo "CONTAINER_ID=$${CONTAINER_ID}" >> $(DEVELOP)/minio

.PHONY: stop
# HELP: stop all currently running develop instances
stop:
	rm -rf develop
.PHONY: stop-%
# HELP(stop-{database,memcached,minio,backend,frontend,hoogle}): stop all currently running develop instances of a given type
stop-%: --stop-%
	rm -rf $(DEVELOP)/$*
stop-container-by-file:
	rm $(CONTAINER_FILE)
stop-container-by-id:
	$(CONTAINER_COMMAND) stop $(CONTAINER_ID)
#	CONTAINER_ID=`grep 'CONTAINER_ID=' $(CONTAINER_FILE) | sed 's/CONTAINER_ID=//'` ; \
#	$(MAKE) stop-container-by-id CONTAINER_ID=$${CONTAINER_ID}

.PHONY: log-%
# HELP(log-{database,memcached,minio,backend,frontend,hoogle}): inspect output of a given (currently running) service. When a service supports multiple running instances in one develop (i.e. backend), you need to specify the exact instance by its associated file (e.g. backend-1, backend-2, etc.), please check the contents of the develop/ directory for a list of running instances.
log-%:
	$(CONTAINER_COMMAND) logs --follow --tail=$(LOGSIZE) `cat develop/$(CURRENT_DEVELOP)/$* | grep CONTAINER_ID= | sed 's/^CONTAINER_ID=//'`

##### CONTAINER TARGETS #####
#############################


# TODO: move targets below to better location

# Some convenience aliases:
.PHONY: hoogle
# HELP: alias for start-hoogle
hoogle: start-hoogle;

.PHONY: i18n-check
# HELP: check internationalization
i18n-check: --image-run---i18n-check
--i18n-check:
	./missing-translations.sh
	@echo No missing translations.

%.lock:
	[ -e $@ ] || touch $@
	flock -en $@ true

.PHONY: --%