#!/bin/bash
# nfsmw-linux-build: builds the native Linux executable of Need for Speed: Most Wanted
# (Black Edition) from YOUR OWN copy of the original Xbox game.
#
# The package ships no game code or proprietary assets. This script:
# 1. Prompts you to pick your Xbox ISO or folder via a file chooser dialog.
# 2. Extracts default.xbe from your USA Black Edition ISO or folder.
# 3. Recompiles the x86 machine code to C++ using XboxXBE-Recomp.
# 4. Compiles the generated C++ files with clang++ / lld.
# 5. Links with the native SDL3 + Vulkan Linux desktop host into:
#      ~/.local/share/nfsmw-linux/bin/MOSTWANTED
#
# Usage:
#   nfsmw-linux-build [--gui] [--jobs N] [--force] [<game .iso or folder>]
set -euo pipefail

VERSION="0.1.7"
DATA="${NFSMW_DATA_DIR:-/usr/share/nfsmw-linux}"
LIB="${NFSMW_LIB_DIR:-/usr/lib/nfsmw-linux}"
HOME_DIR="${NFSMW_HOME:-${XDG_DATA_HOME:-$HOME/.local/share}/nfsmw-linux}"
WORK="${XDG_CACHE_HOME:-$HOME/.cache}/nfsmw-linux"
USA_BLACK_SHA1="f636a16af04171089f879a4bed646e185c734233"
CXX="${CXX:-clang++}"
MARCH="x86-64-v2"

GUI=0; JOBS=""; FORCE=0; SOURCE=""
while (($#)); do
    case "$1" in
        --gui) GUI=1 ;;
        --jobs) shift; JOBS="${1:-}" ;;
        --force) FORCE=1 ;;
        -h|--help) sed -n 2,14p "$0"; exit 0 ;;
        *) SOURCE="$1" ;;
    esac
    shift
done

# If stdout is not a terminal, enable GUI automatically if zenity is installed
if [[ $GUI == 0 ]] && ! [[ -t 1 ]] && command -v zenity >/dev/null 2>&1; then
    GUI=1
fi

say() { echo "  $*"; }
fail() {
    echo ""
    echo "  ========================================================"
    echo -e "  ERROR: $*"
    echo "  ========================================================"
    echo ""
    if [[ $GUI == 1 ]] && command -v zenity >/dev/null 2>&1; then
        zenity --error --title="Need for Speed: Most Wanted (Black Edition)" --width=520 --text="$*" 2>/dev/null || true
    fi
    exit 1
}

mkdir -p "$HOME_DIR/bin" "$HOME_DIR/game" "$WORK" || fail "cannot create $HOME_DIR or $WORK"
LOG="$WORK/build.log"
: > "$LOG"

echo ""
echo "  ======================================================================"
echo "    Need for Speed: Most Wanted Black Edition - Native Linux Builder"
echo "    Version ${VERSION} | Arch: ${MARCH}"
echo "  ======================================================================"
echo ""

# ------------------------------------------------------------------ Toolchain check
for t in "$CXX" ld.lld python3; do
    command -v "$t" >/dev/null 2>&1 || fail "$t is missing. Install via:\n  sudo apt install clang lld python3"
done
VK=""
for f in /usr/lib/x86_64-linux-gnu/libvulkan.so.1 /usr/lib64/libvulkan.so.1 /usr/lib/libvulkan.so.1; do
    [[ -e $f ]] && VK="$f" && break
done
[[ -n $VK ]] || fail "libvulkan.so.1 is missing. Install via:\n  sudo apt install libvulkan1 mesa-vulkan-drivers"

# ------------------------------------------------------------------ Locate or Pick Game Copy
find_game_auto() {
    local dirs=()
    [[ -n ${NFSMW_GAME_DIR:-} ]] && dirs+=("$NFSMW_GAME_DIR")
    dirs+=("$HOME_DIR/game" "$HOME_DIR/assets" "$HOME/Games/MostWanted" "$HOME/Games/NFSMW")
    for d in "${dirs[@]}"; do
        [[ -d $d ]] || continue
        for x in "$d"/default.xbe "$d"/DEFAULT.XBE "$d"/Default.xbe; do
            [[ -f $x ]] && { echo "$x"; return 0; }
        done
        local iso
        iso=$(find -L "$d" -maxdepth 1 -type f \( -iname '*most*wanted*.iso' -o -iname '*nfsmw*.iso' -o -iname '*.iso' \) 2>/dev/null | sort | head -n1)
        [[ -n $iso ]] && { echo "$iso"; return 0; }
    done
    return 1
}

while true; do
    GAME_FILE=""
    if [[ -n $SOURCE ]]; then
        GAME_FILE="$SOURCE"
    elif AUTO=$(find_game_auto 2>/dev/null) && [[ -n $AUTO && -f $AUTO ]]; then
        GAME_FILE="$AUTO"
        echo "Menggunakan game copy yang sudah ada di: $GAME_FILE"
    else
        # If GUI is available, immediately open file chooser so the user does NOT have to place anything manually!
        if command -v zenity >/dev/null 2>&1; then
            PICK=$(zenity --file-selection \
                --title="Pilih File ISO Need for Speed: Most Wanted (Black Edition)" \
                --file-filter="Xbox Game ISO (*.iso, default.xbe) | *.iso *.ISO *.xiso.iso default.xbe DEFAULT.XBE" \
                2>/dev/null || true)
            if [[ -z $PICK ]]; then
                echo "Pemilihan file dibatalkan oleh pengguna."
                exit 0
            fi
            GAME_FILE="$PICK"
        else
            GAME_FILE="$(find_game_auto || true)"
            if [[ -z $GAME_FILE ]]; then
                read -e -p "Masukkan jalur lengkap ke file .iso game Anda: " GAME_FILE
            fi
        fi
    fi

    [[ -n $GAME_FILE ]] || fail "Tidak ada file ISO yang dipilih."
    GAME_FILE=$(realpath "$GAME_FILE" 2>/dev/null || echo "$GAME_FILE")
    [[ -e $GAME_FILE ]] || fail "File '$GAME_FILE' tidak ditemukan."

    if [[ -d $GAME_FILE ]]; then
        if [[ -f $GAME_FILE/default.xbe ]]; then
            ACTUAL_TARGET="$GAME_FILE/default.xbe"
        elif [[ -f $GAME_FILE/DEFAULT.XBE ]]; then
            ACTUAL_TARGET="$GAME_FILE/DEFAULT.XBE"
        else
            fail "Folder '$GAME_FILE' tidak berisi default.xbe."
        fi
        rmdir "$HOME_DIR/game" 2>/dev/null || true
        [[ -e $HOME_DIR/game ]] || ln -s "$GAME_FILE" "$HOME_DIR/game"
        GAME_FILE="$ACTUAL_TARGET"
    fi

    say "File game yang dipilih: $GAME_FILE"
    XBE="$WORK/default.xbe"
    rm -f "$XBE"

    if [[ ${GAME_FILE,,} == *.iso ]]; then
        say "Mengekstrak default.xbe dari disc image Xbox..."
        python3 -I "$DATA/tools/xdvdfs.py" "$GAME_FILE" default.xbe "$XBE" >>"$LOG" 2>&1 \
            || python3 -I "$DATA/tools/xdvdfs.py" "$GAME_FILE" DEFAULT.XBE "$XBE" >>"$LOG" 2>&1 \
            || {
                MSG="Gagal membaca default.xbe dari $GAME_FILE.\nApakah ini file ISO Xbox asli?"
                if [[ $GUI == 1 ]] && command -v zenity >/dev/null 2>&1; then
                    zenity --error --title="Bukan ISO Xbox Asli" --width=500 --text="$MSG" 2>/dev/null || true
                    SOURCE="" # reset to re-prompt
                    continue
                else
                    fail "$MSG"
                fi
            }
    else
        cp -f "$GAME_FILE" "$XBE" || fail "Tidak dapat membaca $GAME_FILE"
    fi

    IMAGE_SHA=$(sha1sum "$XBE" | cut -d' ' -f1)
    say "Executable SHA-1: $IMAGE_SHA"

    # Step 1: Test recompile
    FLAGS="-std=c++20 -O2 -w -fno-strict-aliasing -fsigned-char -march=$MARCH"
    KEY=$( { sha1sum "$LIB/xbe_recomp" "$DATA/config/game.toml"; find "$DATA/sdk" -type f \( -name '*.h' -o -name '*.toml' \) | sort | xargs sha1sum; echo "$FLAGS $IMAGE_SHA $($CXX --version | head -n1)"; } | sha1sum | cut -c1-12)
    GEN="$WORK/gen-$KEY"
    OBJ="$WORK/obj-$KEY"
    mkdir -p "$GEN" "$OBJ"

    say "[1/3] Menjalankan recompiler Xbox XBE ke C++ ..."
    if ! "$LIB/xbe_recomp" "$DATA/config/game.toml" "$XBE" "$GEN" --xdk "$DATA/sdk/xdk" >>"$LOG" 2>&1; then
        LOG_TAIL=$(tail -n 6 "$LOG")
        if echo "$LOG_TAIL" | grep -q "45410083"; then
            ERR_TEXT="File ISO yang Anda pilih berisi game 'BLACK' (FPS), BUKAN 'Need for Speed: Most Wanted'!\n\nDetail:\n• Title ID ISO Anda: 45410083 (BLACK)\n• Title ID yang dibutuhkan: 4541007B (Need for Speed Most Wanted)\n\nSilakan pilih file ISO Need for Speed: Most Wanted Black Edition yang benar."
        else
            ERR_TEXT="Recompiler gagal memproses file ISO ini:\n\n$LOG_TAIL\n\nPastikan file ISO Anda adalah Need for Speed: Most Wanted Black Edition (original Xbox USA release)."
        fi

        if [[ $GUI == 1 ]] && command -v zenity >/dev/null 2>&1; then
            if zenity --question --title="File ISO Tidak Cocok" --width=520 \
                --ok-label="Pilih File ISO Lain" --cancel-label="Keluar" \
                --text="$ERR_TEXT" 2>/dev/null; then
                SOURCE="" # loop back to file chooser
                continue
            else
                exit 1
            fi
        else
            fail "$ERR_TEXT"
        fi
    fi

    # Check symbols
    if ! cmp -s "$GEN/image_symbols.h" "$LIB/image_symbols.h"; then
        fail "Simbol hasil rekompilasi tidak cocok dengan host runtime. Pastikan versi XBE sesuai rilis USA Black Edition."
    fi

    # If we made it here, ISO is valid and recompiled!
    if [[ ${GAME_FILE,,} == *.iso ]]; then
        ln -sf "$GAME_FILE" "$HOME_DIR/game/$(basename "$GAME_FILE")"
    fi
    break
done

# ------------------------------------------------------------------ Step 2: Compile
say "[2/3] Mengompilasi modul C++ game untuk prosesor PC Anda..."
mkdir -p "$OBJ"
SOURCES=("$GEN"/x86_func_mapping.cpp "$GEN"/x86_recomp.*.cpp)
TOTAL=${#SOURCES[@]}
(( TOTAL > 0 )) || fail "Tidak ada file sumber C++ yang dihasilkan di $GEN"

if [[ -z $JOBS ]]; then
    MEM_GB=$(awk '/MemAvailable/ {printf "%d", $2/1048576}' /proc/meminfo)
    JOBS=$(( (MEM_GB - 1) / 2 ))
    (( JOBS < 1 )) && JOBS=1
    (( JOBS > $(nproc) )) && JOBS=$(nproc)
fi

DONE=$(find "$OBJ" -name '*.o' 2>/dev/null | wc -l || echo 0)
if (( DONE >= TOTAL )); then
    say "Semua $TOTAL modul telah dikompilasi sebelumnya: langsung ke tahap link."
else
    say "Mengompilasi $TOTAL modul dengan $JOBS proses paralel (hanya berjalan 1 kali saat pertama)..."
fi

export CXX MARCH GEN OBJ DATA LOG FLAGS
compile_one() {
    local src="$1" o
    o="$OBJ/$(basename "$src" .cpp).o"
    [[ -f $o && $o -nt $src ]] && return 0
    $CXX $FLAGS -I"$DATA/sdk/runtime" -I"$DATA/sdk/harness/compat" -I"$GEN" -c "$src" -o "$o.tmp" 2>>"$LOG" \
        && mv -f "$o.tmp" "$o"
}
export -f compile_one

progress() {
    local done=0
    while (( done < TOTAL )); do
        done=$(find "$OBJ" -name '*.o' 2>/dev/null | wc -l || echo 0)
        local pct=0
        (( TOTAL > 0 )) && pct=$(( done * 100 / TOTAL ))
        echo "$pct"
        echo "# Mengompilasi NFSMW: $done / $TOTAL modul selesai..."
        sleep 2
        kill -0 "$1" 2>/dev/null || break
    done
}

printf '%s\n' "${SOURCES[@]}" | xargs -P "$JOBS" -I{} bash -c 'compile_one "$@"' _ {} &
CPID=$!

if [[ $GUI == 1 ]] && command -v zenity >/dev/null 2>&1; then
    progress $CPID | zenity --progress --title="Need for Speed: Most Wanted" --width=500 --auto-close --no-cancel \
        --text="Mengompilasi game untuk perangkat Linux Anda..." 2>/dev/null || true
    wait $CPID; RC=$?
else
    while kill -0 $CPID 2>/dev/null; do
        n=$(find "$OBJ" -name '*.o' 2>/dev/null | wc -l)
        printf '\r      Proses kompilasi: %d / %d modul' "$n" "$TOTAL"
        sleep 3
    done
    wait $CPID; RC=$?
    echo ""
fi
(( RC == 0 )) || fail "Kompilasi gagal (memori tidak cukup? Coba: nfsmw-linux-build --jobs 1). Cek log: $LOG"

# ------------------------------------------------------------------ Step 3: Link
say "[3/3] Melakukan linking binary Linux native (MOSTWANTED)..."
echo "extern const char kImageSha1[] = \"$IMAGE_SHA\";" > "$WORK/image_sha1.cpp"
$CXX $FLAGS -c "$WORK/image_sha1.cpp" -o "$WORK/image_sha1.o" 2>>"$LOG" || fail "see $LOG"

cat <<'EOF' > "$WORK/host_stubs.cpp"
#include <cstddef>
#include <string>

namespace input {
struct Action { const char* id; const char* label; const char* defaults; };
__attribute__((weak)) const Action* PadActions(size_t* count) { if (count) *count = 0; return nullptr; }
__attribute__((weak)) std::string PadBinding(size_t) { return ""; }
__attribute__((weak)) void PadBind(size_t, const std::string&) {}
__attribute__((weak)) void ResetPadBindings() {}
__attribute__((weak)) void LoadPadBindings() {}
__attribute__((weak)) std::string PressedPadInput() { return ""; }
}

namespace host {
__attribute__((weak)) void SamplePerf() {}
__attribute__((weak)) float CpuLoad() { return -1.0f; }
__attribute__((weak)) float GpuLoad() { return -1.0f; }
__attribute__((weak)) float CpuTemp() { return -1.0f; }
__attribute__((weak)) float GpuTemp() { return -1.0f; }
__attribute__((weak)) float BatteryTemp() { return -1.0f; }
}
EOF
$CXX $FLAGS -c "$WORK/host_stubs.cpp" -o "$WORK/host_stubs.o" 2>>"$LOG" || fail "see $LOG"

OBJS=("$OBJ"/x86_func_mapping.o)
for s in "${SOURCES[@]:1}"; do
    OBJS+=("$OBJ/$(basename "$s" .cpp).o")
done

$CXX -fuse-ld=lld -Wl,--allow-multiple-definition -o "$HOME_DIR/bin/MOSTWANTED.new" \
    "${OBJS[@]}" "$WORK/image_sha1.o" "$WORK/host_stubs.o" \
    -Wl,--whole-archive "$LIB/libnfsmw_host.a" -Wl,--no-whole-archive \
    -Wl,--start-group $(grep '\.a$' "$LIB/link-libs.txt" 2>/dev/null | sed "s|^|$LIB/|") -Wl,--end-group \
    "$VK" -lpthread -ldl -lm -lrt >>"$LOG" 2>&1 || fail "Linking gagal. Cek log: $LOG"

mv -f "$HOME_DIR/bin/MOSTWANTED.new" "$HOME_DIR/bin/MOSTWANTED"
chmod 755 "$HOME_DIR/bin/MOSTWANTED"
STAMP="$VERSION $IMAGE_SHA"
echo "$STAMP" > "$HOME_DIR/bin/.stamp"

# Initialize clean default WASD keyboard bindings if not already customized
CONF="$HOME_DIR/mostwanted.toml"
if ! grep -q "\[keys\]" "$CONF" 2>/dev/null; then
    cat <<'EOF' >> "$CONF"

[keys]
trigger_right = "W"
trigger_left = "S"
move_left = "A"
move_right = "D"
move_up = ""
move_down = ""
button_a = "Space,Enter"
button_b = "Shift"
button_x = "X"
button_y = "C"
button_black = "V"
button_white = "H"
dpad_up = "Up"
dpad_down = "Down"
dpad_left = "Left"
dpad_right = "Right"
start = "Escape"
back = "R"
look_up = "PageUp"
look_down = "PageDown"
settings_panel = "F10,Backtick"
EOF
elif ! grep -q "look_up" "$CONF" 2>/dev/null; then
    sed -i '/\[keys\]/a look_up = "PageUp"\nlook_down = "PageDown"' "$CONF" 2>/dev/null || true
fi

say ""
say "======================================================================"
say "  BERHASIL! Binary native telah dibuat di:"
say "  $HOME_DIR/bin/MOSTWANTED"
say "  Game sekarang siap dimainkan!"
say "======================================================================"
say ""

if [[ $GUI == 1 ]] && command -v zenity >/dev/null 2>&1; then
    zenity --info --title="Need for Speed: Most Wanted" --width=420 \
        --text="Kompilasi berhasil!\nNeed for Speed: Most Wanted Black Edition siap dimainkan." 2>/dev/null &
fi

exit 0
