#!/usr/bin/env bash

set -Eeuo pipefail

# ============================================================
# NextLimit - Génération du dataset routier France
#
# Dépendance requise sur l'hôte :
#
#   Docker
#
# Tout le reste est exécuté dans le conteneur :
#
#   - Python
#   - PyOsmium
#   - osmium-tool
#   - curl
#
# Usage :
#
#   ./fr.sh
#
# Sortie :
#
#   ./data/france-nextlimit.osm.pbf
#
# ============================================================

SCRIPT_DIR="$(
    cd "$(dirname "${BASH_SOURCE[0]}")"
    pwd
)"

DOCKERFILE="${SCRIPT_DIR}/Dockerfile"

IMAGE_NAME="${IMAGE_NAME:-nextlimit-osm-tool:latest}"

DATA_DIR="${DATA_DIR:-${SCRIPT_DIR}/data}"

SOURCE_URL="${SOURCE_URL:-https://download.geofabrik.de/europe/france-latest.osm.pbf}"

KEEP_INTERMEDIATE="${KEEP_INTERMEDIATE:-0}"


# ============================================================
# Helpers
# ============================================================

log() {
    printf '\n\033[1;34m[NextLimit]\033[0m %s\n' "$*"
}

success() {
    printf '\n\033[1;32m[NextLimit]\033[0m %s\n' "$*"
}

die() {
    printf '\n\033[1;31m[ERREUR]\033[0m %s\n' "$*" >&2
    exit 1
}


# ============================================================
# Vérifications
# ============================================================

command -v docker >/dev/null 2>&1 \
    || die "Docker n'est pas installé."

docker info >/dev/null 2>&1 \
    || die "Docker n'est pas démarré ou n'est pas accessible."

[[ -f "${DOCKERFILE}" ]] \
    || die "Dockerfile introuvable : ${DOCKERFILE}"

mkdir -p "${DATA_DIR}"


# ============================================================
# Construction de l'image
#
# On transmet uniquement le Dockerfile à Docker afin de ne pas
# envoyer ./data (qui contiendra plusieurs Go) dans le build
# context.
# ============================================================

log "Construction / vérification de l'image Docker..."

docker build \
    --tag "${IMAGE_NAME}" \
    - < "${DOCKERFILE}"


# ============================================================
# Lancement
# ============================================================

log "Lancement du traitement dans Docker..."

log "Répertoire de données : ${DATA_DIR}"

docker run \
    --rm \
    --interactive \
    --user "$(id -u):$(id -g)" \
    --env "SOURCE_URL=${SOURCE_URL}" \
    --env "KEEP_INTERMEDIATE=${KEEP_INTERMEDIATE}" \
    --volume "${DATA_DIR}:/work/data" \
    "${IMAGE_NAME}" \
    bash -s <<'CONTAINER_SCRIPT'

set -Eeuo pipefail


# ============================================================
# Configuration
# ============================================================

WORK_DIR="/work/data"

SOURCE_FILE="${WORK_DIR}/france-latest.osm.pbf"

FILTERED_FILE="${WORK_DIR}/france-nextlimit-filtered.osm.pbf"

FINAL_FILE="${WORK_DIR}/france-nextlimit.osm.pbf"

FILTER_FILE="${WORK_DIR}/nextlimit-osmium-filter.txt"

PYTHON_FILTER="${WORK_DIR}/nextlimit-strip-tags.py"


log() {
    printf '\n\033[1;34m[Docker / NextLimit]\033[0m %s\n' "$*"
}

success() {
    printf '\n\033[1;32m[Docker / NextLimit]\033[0m %s\n' "$*"
}

die() {
    printf '\n\033[1;31m[Docker / ERREUR]\033[0m %s\n' "$*" >&2
    exit 1
}

human_size() {
    du -h "$1" | awk '{print $1}'
}


# ============================================================
# Versions
# ============================================================

log "Environnement Docker"

echo
python3 --version
osmium --version
echo

python3 - <<'PY'
import osmium
print("PyOsmium : OK")
PY


# ============================================================
# Téléchargement
# ============================================================

log "Téléchargement du PBF France..."

echo "${SOURCE_URL}"
echo

curl \
    --fail \
    --location \
    --retry 10 \
    --retry-delay 5 \
    --retry-all-errors \
    --continue-at - \
    --output "${SOURCE_FILE}" \
    "${SOURCE_URL}"


# ============================================================
# Validation PBF source
# ============================================================

log "Validation du fichier PBF source..."

osmium fileinfo "${SOURCE_FILE}" >/dev/null \
    || die "Le PBF téléchargé est invalide."


SOURCE_SIZE="$(human_size "${SOURCE_FILE}")"
SOURCE_BYTES="$(stat -c%s "${SOURCE_FILE}")"

echo
echo "PBF source : ${SOURCE_SIZE}"


# ============================================================
# Filtre réseau routier
# ============================================================

log "Création du filtre routier..."

cat > "${FILTER_FILE}" <<'EOF'

# ============================================================
# Réseau routier automobile NextLimit
# ============================================================

w/highway=motorway
w/highway=motorway_link

w/highway=trunk
w/highway=trunk_link

w/highway=primary
w/highway=primary_link

w/highway=secondary
w/highway=secondary_link

w/highway=tertiary
w/highway=tertiary_link

w/highway=unclassified

w/highway=residential

w/highway=living_street

w/highway=service

w/highway=road


# ============================================================
# Restrictions de circulation
#
# Exemple :
#
# no_left_turn
# no_right_turn
# only_straight_on
# no_u_turn
#
# Elles sont importantes pour reconstruire correctement
# le graphe routier.
# ============================================================

r/type=restriction
r/type=restriction:*

EOF


# ============================================================
# Extraction Osmium
# ============================================================

log "Extraction du réseau routier automobile..."

rm -f "${FILTERED_FILE}"

osmium tags-filter \
    --expressions="${FILTER_FILE}" \
    --remove-tags \
    --progress \
    --overwrite \
    --output="${FILTERED_FILE}" \
    "${SOURCE_FILE}"


FILTERED_SIZE="$(human_size "${FILTERED_FILE}")"
FILTERED_BYTES="$(stat -c%s "${FILTERED_FILE}")"


echo
echo "Après extraction routière : ${FILTERED_SIZE}"


# ============================================================
# PyOsmium
#
# Suppression des tags inutiles.
# ============================================================

log "Création du filtre PyOsmium..."

cat > "${PYTHON_FILTER}" <<'PY'

#!/usr/bin/env python3

import os
import sys
import osmium


if len(sys.argv) != 3:
    print(
        f"Usage: {sys.argv[0]} INPUT OUTPUT",
        file=sys.stderr,
    )
    sys.exit(1)


INPUT = sys.argv[1]
OUTPUT = sys.argv[2]


# ============================================================
# Tags réellement nécessaires à NextLimit
# ============================================================

WAY_TAGS = {

    # --------------------------------------------------------
    # Classification
    # --------------------------------------------------------

    "highway",

    # --------------------------------------------------------
    # Limitations de vitesse
    # --------------------------------------------------------

    "maxspeed",

    "maxspeed:forward",
    "maxspeed:backward",

    "maxspeed:conditional",

    "maxspeed:type",

    "source:maxspeed",

    "zone:maxspeed",

    # --------------------------------------------------------
    # Direction / graphe
    # --------------------------------------------------------

    "oneway",

    "junction",

    # --------------------------------------------------------
    # Restrictions d'accès
    # --------------------------------------------------------

    "access",

    "vehicle",

    "motor_vehicle",

    "motorcar",

    # --------------------------------------------------------
    # Service roads
    # --------------------------------------------------------

    "service",

    # --------------------------------------------------------
    # Informations utiles au debug / map matching
    #
    # On pourra éventuellement les enlever plus tard
    # pour gagner encore de la place.
    # --------------------------------------------------------

    "ref",

    "name",
}


WAY_PREFIXES = (

    "maxspeed:",

)


RELATION_TAGS = {

    "type",

    "restriction",

    "restriction:conditional",

    "except",
}


RELATION_PREFIXES = (

    "restriction:",

)


# ============================================================
# Utilitaires
# ============================================================

def filter_tags(tags, allowed, prefixes=()):

    result = {}

    for tag in tags:

        key = tag.k
        value = tag.v

        if key in allowed:
            result[key] = value
            continue

        if any(
            key.startswith(prefix)
            for prefix in prefixes
        ):
            result[key] = value

    return result


# ============================================================
# Handler
# ============================================================

class NextLimitHandler(osmium.SimpleHandler):

    def __init__(self, writer):

        super().__init__()

        self.writer = writer

        self.nodes = 0
        self.ways = 0
        self.relations = 0

        self.kept_tags = 0
        self.removed_tags = 0


    # --------------------------------------------------------
    # NODE
    #
    # Les nodes sont nécessaires à la géométrie des ways.
    #
    # Par contre leurs tags ne sont pas nécessaires pour
    # NextLimit à ce stade.
    # --------------------------------------------------------

    def node(self, node):

        self.nodes += 1

        original = len(node.tags)

        self.removed_tags += original

        if original:

            node = node.replace(tags={})

        self.writer.add_node(node)


    # --------------------------------------------------------
    # WAY
    # --------------------------------------------------------

    def way(self, way):

        self.ways += 1

        tags = filter_tags(
            way.tags,
            WAY_TAGS,
            WAY_PREFIXES,
        )

        original = len(way.tags)

        self.kept_tags += len(tags)

        self.removed_tags += (
            original - len(tags)
        )

        self.writer.add_way(
            way.replace(tags=tags)
        )


    # --------------------------------------------------------
    # RELATION
    # --------------------------------------------------------

    def relation(self, relation):

        self.relations += 1

        tags = filter_tags(
            relation.tags,
            RELATION_TAGS,
            RELATION_PREFIXES,
        )

        original = len(relation.tags)

        self.kept_tags += len(tags)

        self.removed_tags += (
            original - len(tags)
        )

        self.writer.add_relation(
            relation.replace(tags=tags)
        )


# ============================================================
# Traitement
# ============================================================

if os.path.exists(OUTPUT):
    os.unlink(OUTPUT)


writer = osmium.SimpleWriter(OUTPUT)

handler = NextLimitHandler(writer)

try:

    handler.apply_file(
        INPUT,
        locations=False,
    )

finally:

    writer.close()


# ============================================================
# Statistiques
# ============================================================

print()

print(
    "============================================================"
)

print(
    "           STATISTIQUES NEXTLIMIT"
)

print(
    "============================================================"
)

print()

print(
    f"Nodes       : {handler.nodes:,}"
)

print(
    f"Ways        : {handler.ways:,}"
)

print(
    f"Relations   : {handler.relations:,}"
)

print(
    f"Tags gardés : {handler.kept_tags:,}"
)

print(
    f"Tags retirés: {handler.removed_tags:,}"
)

print()

PY


# ============================================================
# Exécution PyOsmium
# ============================================================

log "Nettoyage des tags..."

rm -f "${FINAL_FILE}"

python3 \
    "${PYTHON_FILTER}" \
    "${FILTERED_FILE}" \
    "${FINAL_FILE}"


# ============================================================
# Vérification
# ============================================================

log "Vérification de l'intégrité du PBF final..."

osmium fileinfo "${FINAL_FILE}" >/dev/null \
    || die "Le PBF final est invalide."

osmium check-refs "${FINAL_FILE}" \
    || die "Le PBF final contient des références invalides."


# ============================================================
# Statistiques tailles
# ============================================================

FINAL_SIZE="$(human_size "${FINAL_FILE}")"

FINAL_BYTES="$(stat -c%s "${FINAL_FILE}")"


SOURCE_TO_FILTERED="$(
    awk \
        -v source="${SOURCE_BYTES}" \
        -v final="${FILTERED_BYTES}" \
        'BEGIN {
            printf "%.2f", (1 - final/source) * 100
        }'
)"


SOURCE_TO_FINAL="$(
    awk \
        -v source="${SOURCE_BYTES}" \
        -v final="${FINAL_BYTES}" \
        'BEGIN {
            printf "%.2f", (1 - final/source) * 100
        }'
)"


# ============================================================
# SHA256
# ============================================================

log "Calcul SHA256..."

sha256sum "${FINAL_FILE}" \
    > "${FINAL_FILE}.sha256"


# ============================================================
# Résultat
# ============================================================

echo

echo "============================================================"

echo "                 NEXTLIMIT FRANCE"

echo "============================================================"

echo

printf "%-30s %10s\n" \
    "PBF Geofabrik" \
    "${SOURCE_SIZE}"

printf "%-30s %10s\n" \
    "Réseau routier filtré" \
    "${FILTERED_SIZE}"

printf "%-30s %10s\n" \
    "Dataset NextLimit" \
    "${FINAL_SIZE}"

echo

echo "Réduction Geofabrik -> réseau : ${SOURCE_TO_FILTERED} %"

echo "Réduction Geofabrik -> final  : ${SOURCE_TO_FINAL} %"

echo

echo "Dataset :"

echo "  ${FINAL_FILE}"

echo

echo "Checksum :"

cat "${FINAL_FILE}.sha256"

echo


# ============================================================
# Nettoyage intermédiaire
# ============================================================

if [[ "${KEEP_INTERMEDIATE:-0}" != "1" ]]; then

    log "Suppression du fichier intermédiaire..."

    rm -f "${FILTERED_FILE}"

else

    log "KEEP_INTERMEDIATE=1 : conservation du fichier intermédiaire."

fi


success "Dataset France généré avec succès."

CONTAINER_SCRIPT


# ============================================================
# Résultat hôte
# ============================================================

success "Traitement terminé."

echo

echo "Fichiers :"

ls -lh \
    "${DATA_DIR}/france-latest.osm.pbf" \
    "${DATA_DIR}/france-nextlimit.osm.pbf" \
    "${DATA_DIR}/france-nextlimit.osm.pbf.sha256"

echo
