# set shell := [ "bash", "-uc" ]
_default:
    @- just --unsorted --list
menu:
    @- just --unsorted --choose
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Justfile
# NOTE: Do not change the contents of this file!
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# VARIABLES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

OS := if os_family() == "windows" { "windows" } else { "linux" }
PYTHON := if os_family() == "windows" { "py -3" } else { "python3" }
GEN_MODELS := "datamodel-codegen"
GEN_MODELS_DOCUMENTATION := "openapi-generator"
PORT := "8000"
URL_API := "http://127.0.0.1:8000/docs"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Macros
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

_create-file-if-not-exists fname:
    @touch "{{fname}}";

_create-folder-if-not-exists path:
    @if ! [[ -d "{{path}}" ]]; then mkdir -p "{{path}}"; fi

_delete-if-file-exists fname:
    @if [[ -f "{{fname}}" ]]; then rm "{{fname}}"; fi

_delete-if-folder-exists path:
    @if [[ -d "{{path}}" ]]; then rm -rf "{{path}}"; fi

_clean-all-files path pattern:
    @find {{path}} -type f -name "{{pattern}}" -exec basename {} \; 2> /dev/null
    @- find {{path}} -type f -name "{{pattern}}" -exec rm {} \; 2> /dev/null

_clean-all-folders path pattern:
    @find {{path}} -type d -name "{{pattern}}" -exec basename {} \; 2> /dev/null
    @- find {{path}} -type d -name "{{pattern}}" -exec rm -rf {} \; 2> /dev/null

_copy-file-if-not-exists path_from path_to:
    @- cp -n "{{path_from}}" "{{path_to}}"

_check-python-tool tool name:
    #!/usr/bin/env bash
    success=false
    {{tool}} --help >> /dev/null 2> /dev/null && success=true;
    # NOTE: if exitcode is 251 (= help or print version), then render success.
    [[ "$?" == "251" ]] && success=true;
    # FAIL tool not installed
    if ( $success ); then
        echo -e "Tool \x1b[2;3m{{tool}}\x1b[0m installed correctly.";
        exit 0;
    else
        echo -e "Tool \x1b[2;3m{{tool}}\x1b[0m did not work." >> /dev/stderr;
        echo -e "Ensure that \x1b[2;3m{{name}}\x1b[0m (-> \x1b[1mjust build\x1b[0m) installed correctly and system paths are set." >> /dev/stderr;
        exit 1;
    fi

_generate-models path name:
    @{{GEN_MODELS}} \
        --input-file-type openapi \
        --encoding "UTF-8" \
        --disable-timestamp \
        --use-schema-description \
        --set-default-enum-member \
        --allow-population-by-field-name \
        --snake-case-field \
        --strict-nullable \
        --target-python-version 3.10 \
        --input {{path}}/{{name}}-schema.yaml \
        --output {{path}}/generated/{{name}}.py

_generate-models-documentation path_schema path_docs name:
    @{{GEN_MODELS_DOCUMENTATION}} generate \
        --input-spec {{path_schema}}/{{name}}-schema.yaml \
        --generator-name markdown \
        --output "{{path_docs}}/{{name}}"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: build
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

build:
    @just build-misc
    @just build-requirements
    @just _check-system-requirements
    @just build-models
build-misc:
    @# create database if not exists:
    @just _create-folder-if-not-exists "data"
    @# copy template of configs to setup if not exists:
    @just _create-folder-if-not-exists "setup"
    @just _copy-file-if-not-exists "templates/template-config.yaml" "setup/config.yaml"
    @just _copy-file-if-not-exists "templates/template.env" ".env"
build-requirements:
    @{{PYTHON}} -m pip install --disable-pip-version-check -r requirements.txt
    @{{PYTHON}} -m jupyter nbextension enable --py widgetsnbextension
build-models:
    @echo "Generate data models from schemata."
    @just _delete-if-folder-exists "models/generated"
    @just _create-folder-if-not-exists "models/generated"
    @- #just _generate-models "models" "config"
build-documentation:
    @echo "Generate documentations data models from schemata."
    @just _delete-if-folder-exists "docs"
    @just _create-folder-if-not-exists "docs"
    @- #just _generate-models-documentation "models" "docs" "config"
    @- just _clean-all-files "." .openapi-generator*
    @- just _clean-all-folders "." .openapi-generator*
dist:
    @just build
    @just build-documentation

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: run
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# runs using commandline interface
run:
    @# create config/data folders if missing:
    @just build-misc
    @# run source code als cli
    @{{PYTHON}} main.py

# runs python notebook (in browser)
notebook name="main":
    @# create config/data folders if missing:
    @just build-misc
    @# run notebook
    @{{PYTHON}} -m jupyter notebook notebooks/{{name}}.ipynb

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: tests
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tests: tests-unit tests-integration
tests-logs:
    @just _create-logs
    @- just tests
    @just _display-logs
tests-unit-logs:
    @just _create-logs
    @- just tests-unit
    @just _display-logs
tests-integration-logs:
    @just _create-logs
    @- just tests-integration
    @just _display-logs
tests-unit:
    @{{PYTHON}} -m pytest tests \
        --ignore=tests/integration \
        --cov-reset \
        --cov=. \
        2> /dev/null
tests-integration:
    @{{PYTHON}} -m pytest tests/integration 2> /dev/null

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: qa
# NOTE: use for development only.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

qa:
    @{{PYTHON}} -m coverage report -m
coverage source_path tests_path:
    @just _create-logs
    @-just _coverage-no-logs "{{source_path}}" "{{tests_path}}"
    @just _display-logs
_coverage-no-logs source_path tests_path:
    @{{PYTHON}} -m pytest {{tests_path}} \
        --ignore=tests/integration \
        --cov-reset \
        --cov={{source_path}} \
        --capture=tee-sys \
        2> /dev/null

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: clean
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

clean:
    @just clean-basic
    @just pre-commit
pre-commit:
    @echo "Clean python notebooks."
    @{{PYTHON}} -m jupyter nbconvert --clear-output --inplace **/*.ipynb
    @- {{PYTHON}} -m jupytext --update-metadata '{"vscode":""}' **/*.ipynb 2> /dev/null
    @- {{PYTHON}} -m jupytext --update-metadata '{"vscode":null}' **/*.ipynb 2> /dev/null
clean-basic:
    @echo "All system artefacts will be force removed."
    @- just _clean-all-files "." ".DS_Store" 2> /dev/null
    @echo "All build artefacts will be force removed."
    @- just _clean-all-folders "." "__pycache__" 2> /dev/null
    @- just _delete-if-folder-exists "models/generated" 2> /dev/null
    @echo "All test artefacts will be force removed."
    @- just _clean-all-folders "." ".pytest_cache" 2> /dev/null
    @- just _delete-if-file-exists ".coverage" 2> /dev/null
    @- just _delete-if-folder-exists "logs" 2> /dev/null

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: logging
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

_create-logs:
    @# For logging purposes (since stdout is rechanneled):
    @just _delete-if-file-exists "logs/debug.log"
    @just _create-folder-if-not-exists "logs"
    @just _create-file-if-not-exists "logs/debug.log"
_display-logs:
    @echo ""
    @echo "Content of logs/debug.log:"
    @echo "----------------"
    @echo ""
    @- cat logs/debug.log
    @echo ""
    @echo "----------------"
logs:
    @just _create-logs
    @tail -f logs/debug.log &

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: processes
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

api-process:
    #!/usr/bin/env bash
    if [[ "{{OS}}" == "linux" ]]; then
        ps aux | grep "uvicorn src.api:app"
    else
        netstat -ano | findstr "uvicorn src.api:app"
    fi
kill-api-process:
    #!/usr/bin/env bash
    # NOTE: only need to do this for linux.
    if [[ "{{OS}}" == "linux" ]]; then
        echo "Terminating all processes associated to app and port {{PORT}}."
        while read pid; do
            if [[ "$pid" == "" ]]; then continue; fi
            echo "- killing process $pid:"
            kill -9 ${pid};
        done <<< $( pgrep -f "uvicorn src.api:app --port {{PORT}}" )
    fi

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: requirements
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

_check-system:
    @echo "Operating System detected: {{os_family()}}."
    @echo "Python command used: {{PYTHON}}."

_check-system-requirements:
    @just _check-python-tool "{{GEN_MODELS}}" "datamodel-code-generator"
    @just _check-python-tool "{{GEN_MODELS_DOCUMENTATION}}" "openapi-code-generator"