#!/bin/bash
# Copyright (C) 2026 Ralf Burger — GPL v3
# obd_flash.sh -- Kompilieren und Flashen: lokal oder via SSH auf PC2
#                 Unterstützt mehrere Instanzen desselben Board-Typs
#
# Verwendung:
#   ./obd_flash.sh bridge                              # Bridge via PC2
#   ./obd_flash.sh --local bridge                     # Bridge lokal
#   ./obd_flash.sh --local all                        # alle lokal
#   ./obd_flash.sh --flash-only bridge                # nur flashen
#   ./obd_flash.sh --no-monitor bridge                # ohne Serial Monitor
#
#   Mehrere Instanzen (einmal kompilieren, mehrfach flashen):
#   ./obd_flash.sh --port bridge:0=/dev/ttyUSB2 --port bridge:1=/dev/ttyUSB3 bridge:0 bridge:1
#
#   ./obd_identify.sh  ->  gibt fertigen Flash-Befehl aus

# =============================================================
# Konfiguration
# =============================================================

PC2_HOST="192.168.28.16"
PC2_USER="root"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if   [ -d "$SCRIPT_DIR/obd_bridge" ];     then SKETCH_BASE="$SCRIPT_DIR"
elif [ -d "$SCRIPT_DIR/src/obd_bridge" ]; then SKETCH_BASE="$SCRIPT_DIR/src"
else                                           SKETCH_BASE="$HOME/Arduino/OBD"
fi

BAUD=921600
LOCAL_MODE=false
FLASH_ONLY=false
NO_MONITOR=false

# Board-Definitionen: Basis-Typ → Eigenschaften
declare -A BOARD_FQBN BOARD_SKETCH BOARD_PART BOARD_FLASH_OFFSET BOARD_CHIP
# Port-Overrides: Instanz-Key → Port  (z.B. "bridge:0" → "/dev/ttyUSB2")
declare -A INSTANCE_PORT
# Standard-Ports für einfache Nutzung
declare -A DEFAULT_PORT

BOARD_FQBN[bridge]="esp32:esp32:heltec_wifi_lora_32_V3"
DEFAULT_PORT[bridge]="/dev/ttyUSB1"
BOARD_SKETCH[bridge]="$SKETCH_BASE/obd_bridge"
BOARD_PART[bridge]="huge_app"
BOARD_FLASH_OFFSET[bridge]="0x10000"
BOARD_CHIP[bridge]="esp32s3"

BOARD_FQBN[receiver]="esp32:esp32:heltec_wifi_lora_32_V3"
DEFAULT_PORT[receiver]="/dev/ttyUSB2"
BOARD_SKETCH[receiver]="$SKETCH_BASE/obd_lora_receiver"
BOARD_PART[receiver]="huge_app"
BOARD_FLASH_OFFSET[receiver]="0x10000"
BOARD_CHIP[receiver]="esp32s3"

BOARD_FQBN[simulator]="esp32:esp32:esp32"
DEFAULT_PORT[simulator]="/dev/ttyUSB0"
BOARD_SKETCH[simulator]="$SKETCH_BASE/obd_simulator"
BOARD_PART[simulator]="default"
BOARD_FLASH_OFFSET[simulator]="0x10000"
BOARD_CHIP[simulator]="esp32"

# =============================================================
# Hilfsfunktionen
# =============================================================

RED='\033[0;31m'; GRN='\033[0;32m'; YLW='\033[1;33m'
BLU='\033[0;34m'; NC='\033[0m'

ok()   { echo -e "${GRN}  ✓ $*${NC}"; }
err()  { echo -e "${RED}  ✗ $*${NC}"; }
info() { echo -e "${BLU}  → $*${NC}"; }
hdr()  { echo -e "\n${YLW}=== $* ===${NC}"; }

# Basis-Typ aus Instanz-Key extrahieren: "bridge:0" → "bridge", "bridge" → "bridge"
base_type() { echo "${1%%:*}"; }

# Gibt "esptool" oder "esptool.py" zurück
esptool_bin() {
    command -v esptool &>/dev/null && echo "esptool" || echo "esptool.py"
}

# Versionsnummer ermitteln (lokal oder remote)
esptool_major() {
    local remote=${1:-false}
    local bin; bin=$(esptool_bin)
    local ver
    if $remote; then
        ver=$(ssh "$PC2_USER@$PC2_HOST" "$bin version 2>/dev/null | head -1")
    else
        ver=$($bin version 2>/dev/null | head -1)
    fi
    echo "$ver" | grep -oP 'v\K[0-9]+' | head -1
}

# Flash-Kommando passend zur esptool-Version
build_flash_cmd() {
    local chip=$1 port=$2 offset=$3 remote=${4:-false}
    local bin; bin=$(esptool_bin)
    local major; major=$(esptool_major "$remote")
    if [ "${major:-4}" -ge 5 ] 2>/dev/null; then
        echo "$bin --chip $chip --port $port --baud $BAUD \
--before default-reset --after hard-reset \
write-flash -z --flash-mode dio --flash-freq 80m \
--flash-size detect $offset"
    else
        echo "$bin --chip $chip --port $port --baud $BAUD \
--before default_reset --after hard_reset \
write_flash -z --flash_mode dio --flash_freq 80m \
--flash_size detect $offset"
    fi
}

# Port für eine Instanz ermitteln
get_port() {
    local instance=$1
    local base; base=$(base_type "$instance")
    # Expliziter Override hat Vorrang
    if [ -n "${INSTANCE_PORT[$instance]}" ]; then
        echo "${INSTANCE_PORT[$instance]}"
    elif [ -n "${INSTANCE_PORT[$base]}" ]; then
        echo "${INSTANCE_PORT[$base]}"
    else
        echo "${DEFAULT_PORT[$base]}"
    fi
}

check_deps() {
    if ! $FLASH_ONLY && ! command -v arduino-cli &>/dev/null; then
        err "arduino-cli nicht gefunden"
        echo "    https://arduino.github.io/arduino-cli/latest/installation/"
        exit 1
    fi
    if $LOCAL_MODE; then
        if ! command -v esptool &>/dev/null && ! command -v esptool.py &>/dev/null; then
            err "esptool nicht gefunden (lokal)"
            echo "    pip install esptool   oder   sudo apt install esptool"
            exit 1
        fi
    else
        if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$PC2_USER@$PC2_HOST" true 2>/dev/null; then
            err "SSH zu $PC2_USER@$PC2_HOST fehlgeschlagen"
            echo "    ssh-copy-id $PC2_USER@$PC2_HOST"
            exit 1
        fi
        if ! ssh "$PC2_USER@$PC2_HOST" \
            "command -v esptool || command -v esptool.py" &>/dev/null; then
            err "esptool auf PC2 nicht gefunden"
            echo "    pip install esptool"
            exit 1
        fi
    fi
}

# Kompiliert einen Basis-Typ (einmalig) — Binary landet in /tmp/obd_build_<type>/
compile_type() {
    local base=$1
    local fqbn="${BOARD_FQBN[$base]}"
    local sketch="${BOARD_SKETCH[$base]}"
    local part="${BOARD_PART[$base]}"
    local builddir="/tmp/obd_build_${base}"

    local build_ts sketch_ts
    build_ts=$(date '+%Y-%m-%d_%H:%M:%S')
    sketch_ts=$(find "$sketch" -maxdepth 1 \
        \( -name "*.ino" -o -name "*.h" -o -name "*.cpp" \) \
        -printf '%TY-%Tm-%Td_%TH:%TM:%TS\n' 2>/dev/null | sort -r | head -1 | cut -d. -f1)
    [ -z "$sketch_ts" ] && sketch_ts="unbekannt"

    info "Kompiliere ${base}  (${build_ts//_/ })..."
    mkdir -p "$builddir"

    if ! arduino-cli compile \
        --fqbn "$fqbn" \
        --build-property "build.partitions=$part" \
        --build-property \
          "compiler.cpp.extra_flags=-DBUILD_TIME=\"${build_ts}\" -DSKETCH_MTIME=\"${sketch_ts}\"" \
        --output-dir "$builddir" \
        "$sketch" 2>&1; then
        err "Kompilierung fehlgeschlagen: $base"
        return 1
    fi
    ok "Kompilierung OK: $base"
    return 0
}

# Flasht eine Instanz mit dem bereits kompilierten Binary des Basis-Typs
flash_instance() {
    local instance=$1
    local base; base=$(base_type "$instance")
    local port; port=$(get_port "$instance")
    local chip="${BOARD_CHIP[$base]}"
    local offset="${BOARD_FLASH_OFFSET[$base]}"
    local builddir="/tmp/obd_build_${base}"

    hdr "Flash: $instance  →  $port  ($( $LOCAL_MODE && echo 'lokal' || echo "PC2: $PC2_USER@$PC2_HOST" ))"

    # Binary finden
    local binary
    binary=$(find "$builddir" -name "*.bin" ! -name "*bootloader*" \
             ! -name "*partitions*" 2>/dev/null | head -1)
    if [ -z "$binary" ]; then
        err "Kein Binary in $builddir"
        if $FLASH_ONLY; then
            err "Hinweis: zuerst ohne --flash-only kompilieren"
        fi
        return 1
    fi
    info "Binary : $(basename "$binary")  ($(du -h "$binary" | cut -f1))"
    $FLASH_ONLY && info "Alter  : $(date -r "$binary" '+%Y-%m-%d %H:%M:%S')"

    if $LOCAL_MODE; then
        info "Flashe lokal ($port)..."
        echo ""
        local flash_cmd
        flash_cmd=$(build_flash_cmd "$chip" "$port" "$offset" false)
        if $flash_cmd "$binary"; then
            echo ""; ok "Flash OK: $instance  ($port)"
        else
            echo ""; err "Flash fehlgeschlagen: $instance"; return 1
        fi
    else
        info "Kopiere zu PC2..."
        local tmpbin="/tmp/obd_${base}_${instance//:/}.bin"
        if ! scp -q "$binary" "$PC2_USER@$PC2_HOST:$tmpbin"; then
            err "SCP fehlgeschlagen"; return 1
        fi
        ok "Binary auf PC2 bereit"

        info "Flashe auf PC2 ($port)..."
        echo ""
        local remote_flash_cmd
        remote_flash_cmd=$(build_flash_cmd "$chip" "$port" "$offset" true)
        if ssh "$PC2_USER@$PC2_HOST" "$remote_flash_cmd $tmpbin"; then
            echo ""; ok "Flash OK: $instance  ($port)"
        else
            echo ""; err "Flash fehlgeschlagen: $instance"; return 1
        fi
    fi

    return 0
}

usage() {
    echo ""
    echo "Verwendung: $0 [Optionen] <board> [board2 ...]"
    echo ""
    echo "  Boards        : bridge | receiver | simulator | all"
    echo "  Mehrfach      : bridge:0 bridge:1  (nach --port Angabe)"
    echo ""
    echo "  Optionen:"
    echo "    --local                         Lokal kompilieren + flashen (kein SSH)"
    echo "    --flash-only                    Nur flashen, nicht neu kompilieren"
    echo "    --no-monitor                    Kein Serial Monitor nach dem Flash"
    echo "    --port bridge=/dev/ttyUSB2      Port-Override (einfach)"
    echo "    --port bridge:0=/dev/ttyUSB2    Port für Instanz 0"
    echo "    --port bridge:1=/dev/ttyUSB3    Port für Instanz 1"
    echo ""
    echo "  Beispiele:"
    echo "    $0 all"
    echo "    $0 --local bridge"
    echo "    $0 --local --flash-only receiver"
    echo "    $0 --local --port bridge:0=/dev/ttyUSB2 --port bridge:1=/dev/ttyUSB3 bridge:0 bridge:1"
    echo ""
    echo "  Port-Zuordnung automatisch erkennen:"
    echo "    ./obd_identify.sh"
    echo ""
    echo "  Konfiguration:"
    echo "    PC2_HOST = $PC2_HOST  |  PC2_USER = $PC2_USER"
    echo "    Sketches = $SKETCH_BASE/"
    exit 1
}

# =============================================================
# Main
# =============================================================

[ $# -eq 0 ] && usage

instances=()  # Liste der zu flashenden Instanzen (z.B. "bridge" "bridge:0" "receiver")

while [ $# -gt 0 ]; do
    case "$1" in
        --local)      LOCAL_MODE=true;  shift ;;
        --flash-only) FLASH_ONLY=true;  shift ;;
        --no-monitor) NO_MONITOR=true;  shift ;;
        --port)
            # Format: bridge=/dev/ttyUSB2  oder  bridge:0=/dev/ttyUSB2
            IFS='=' read -r ikey iport <<< "$2"
            local_base=$(base_type "$ikey")
            if [ -z "${BOARD_FQBN[$local_base]+x}" ]; then
                err "Unbekannter Board-Typ in --port: $local_base"
                usage
            fi
            INSTANCE_PORT[$ikey]="$iport"
            info "Port: $ikey → $iport"
            shift 2
            ;;
        all)
            instances=(bridge receiver simulator)
            shift
            ;;
        bridge|receiver|simulator|bridge:*|receiver:*|simulator:*)
            instances+=("$1")
            shift
            ;;
        --help|-h) usage ;;
        *) err "Unbekanntes Argument: $1"; usage ;;
    esac
done

[ ${#instances[@]} -eq 0 ] && usage

echo ""
echo -e "${YLW}======================================="
echo "  obdBT Flash-Script"
$LOCAL_MODE  && echo "  Modus : LOKAL" || echo "  Modus : PC2  ($PC2_USER@$PC2_HOST)"
$FLASH_ONLY  && echo "  Kompilierung: ÜBERSPRUNGEN (--flash-only)"
echo -e "=======================================${NC}"

check_deps

# Basis-Typen die kompiliert werden müssen (dedupliziert)
declare -A compiled_ok  # compiled_ok[bridge]=1 wenn erfolgreich

if ! $FLASH_ONLY; then
    hdr "Kompilierung"
    declare -A to_compile
    for inst in "${instances[@]}"; do
        base=$(base_type "$inst")
        to_compile[$base]=1
    done
    for base in "${!to_compile[@]}"; do
        if ! compile_type "$base"; then
            compiled_ok[$base]=0
        else
            compiled_ok[$base]=1
        fi
    done
else
    # Flash-only: alle als "kompiliert" markieren
    for inst in "${instances[@]}"; do
        base=$(base_type "$inst")
        compiled_ok[$base]=1
    done
fi

# Flashen
failed=()
for inst in "${instances[@]}"; do
    base=$(base_type "$inst")
    if [ "${compiled_ok[$base]:-0}" -eq 0 ]; then
        err "Überspringe $inst — Kompilierung fehlgeschlagen"
        failed+=("$inst")
        continue
    fi
    if ! flash_instance "$inst"; then
        failed+=("$inst")
    fi
done

# Serial Monitor — nur bei einzelner Instanz
if [ ${#instances[@]} -eq 1 ] && ! $NO_MONITOR && [ ${#failed[@]} -eq 0 ]; then
    inst="${instances[0]}"
    port=$(get_port "$inst")
    info "Serial Monitor  —  Beenden: Ctrl+A Ctrl+X  (oder: pkill picocom)"
    sleep 1
    if $LOCAL_MODE; then
        picocom -b 115200 --flow n --imap lfcrlf "$port" 2>/dev/null || \
        minicom -D "$port" -b 115200 || \
        screen "$port" 115200
    else
        ssh -t "$PC2_USER@$PC2_HOST" bash -c \
            "'picocom -b 115200 --flow n --imap lfcrlf $port || \
              minicom -D $port -b 115200 || \
              screen $port 115200'"
    fi
fi

# Zusammenfassung
echo ""
echo -e "${YLW}======================================="
echo "  Zusammenfassung"
echo -e "=======================================${NC}"
for inst in "${instances[@]}"; do
    port=$(get_port "$inst")
    if [[ " ${failed[*]} " == *" $inst "* ]]; then
        err "$inst: FEHLGESCHLAGEN  ($port)"
    else
        ok  "$inst: OK  ($port)"
    fi
done
echo ""
