#!/usr/bin/env bash
# CDCamera v1.61 - Camera & FoV Mod for Crimson Desert
# Cross-platform installer for Linux and macOS (via Proton/Wine)
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PRESETS_DIR="$SCRIPT_DIR/../presets"
# Default: Steam version values (overridden by PAMT detection below)
PAZ_OFFSET=$((0x0DD82490))
EXPECTED_SIZE=40905

# ============================================================
# Colors
# ============================================================
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'

echo ""
echo -e "${BOLD}============================================================${NC}"
echo -e "${BOLD}  CDCamera v1.61 - Camera & FoV Mod for Crimson Desert${NC}"
echo -e "${BOLD}============================================================${NC}"
echo ""

# ============================================================
# Game detection
# ============================================================
find_game() {
    # Method 1: Relative to script (mod inside game folder)
    local up="$(dirname "$SCRIPT_DIR")"
    if [[ -f "$up/bin64/CrimsonDesert.exe" ]]; then
        echo "$up"; return
    fi

    # Method 2: Steam on Linux (Proton)
    local steam_paths=(
        "$HOME/.steam/steam/steamapps/common/Crimson Desert"
        "$HOME/.local/share/Steam/steamapps/common/Crimson Desert"
        "$HOME/.steam/root/steamapps/common/Crimson Desert"
    )
    for p in "${steam_paths[@]}"; do
        if [[ -f "$p/bin64/CrimsonDesert.exe" ]]; then
            echo "$p"; return
        fi
    done

    # Method 3: macOS Steam
    local mac_paths=(
        "$HOME/Library/Application Support/Steam/steamapps/common/Crimson Desert"
        "/Applications/Steam.app/Contents/MacOS/steamapps/common/Crimson Desert"
    )
    for p in "${mac_paths[@]}"; do
        if [[ -f "$p/bin64/CrimsonDesert.exe" ]]; then
            echo "$p"; return
        fi
    done

    # Method 4: Proton prefix (flatpak Steam)
    local flatpak="$HOME/.var/app/com.valvesoftware.Steam/.steam/steam/steamapps/common/Crimson Desert"
    if [[ -f "$flatpak/bin64/CrimsonDesert.exe" ]]; then
        echo "$flatpak"; return
    fi

    return 1
}

GAMEDIR=""
if GAMEDIR="$(find_game)"; then
    echo -e "  Game found: ${GREEN}$GAMEDIR${NC}"
else
    echo -e "  ${YELLOW}Could not auto-detect game directory.${NC}"
    echo -n "  Enter game path: "
    read -r GAMEDIR
    GAMEDIR="${GAMEDIR%/}"
    if [[ ! -f "$GAMEDIR/bin64/CrimsonDesert.exe" ]]; then
        echo -e "  ${RED}Invalid path. CrimsonDesert.exe not found.${NC}"
        exit 1
    fi
fi

PAZ="$GAMEDIR/0010/0.paz"
PAMT="$GAMEDIR/0010/0.pamt"
if [[ ! -f "$PAZ" ]]; then
    echo -e "  ${RED}ERROR: $PAZ not found${NC}"
    exit 1
fi

# Dynamic PAZ offset detection via Python (if available)
if [[ -f "$PAMT" ]] && command -v python3 &>/dev/null; then
    PAMT_RESULT=$(python3 -c "
import struct
with open('$PAMT','rb') as f: d=f.read()
o=0;o+=4
pc=struct.unpack_from('<I',d,o)[0];o+=4;o+=8
for i in range(pc):
    o+=4;o+=4
    if i<pc-1: o+=4
fs=struct.unpack_from('<I',d,o)[0];o+=4;o+=fs
ns=struct.unpack_from('<I',d,o)[0];o+=4;nst=o
nodes={}
while o<nst+ns:
    r=o-nst;p=struct.unpack_from('<I',d,o)[0]
    sl=d[o+4];nm=d[o+5:o+5+sl].decode('utf-8','replace')
    nodes[r]=(p,nm);o+=5+sl
fc=struct.unpack_from('<I',d,o)[0];o+=4;o+=4;o+=fc*16
while o+20<=len(d):
    nr,po,cs,os2,fl=struct.unpack_from('<IIIII',d,o);o+=20
    parts=[];c=nr;g=0
    while c!=0xFFFFFFFF and g<64:
        if c not in nodes: break
        parts.append(nodes[c][1]);c=nodes[c][0];g+=1
    path=''.join(reversed(parts))
    if path=='playercamerapreset.xml':
        print(f'{po} {cs}');break
" 2>/dev/null || echo "")
    if [[ -n "$PAMT_RESULT" ]]; then
        PAZ_OFFSET=$(echo "$PAMT_RESULT" | awk '{print $1}')
        EXPECTED_SIZE=$(echo "$PAMT_RESULT" | awk '{print $2}')
        echo -e "  PAZ offset: ${PAZ_OFFSET}, size: ${EXPECTED_SIZE}"
    fi
fi
echo ""

# ============================================================
# Menu functions
# ============================================================
choose() {
    local prompt="$1"; shift
    local -a options=("$@")
    echo -e "  ${BOLD}$prompt${NC}"
    echo ""
    local i=0
    for opt in "${options[@]}"; do
        echo "    [$i] $opt"
        ((i++))
    done
    echo ""
    echo -n "  Your choice: "
    read -r choice
    echo ""
}

# ============================================================
# Step 1: Camera Style
# ============================================================
echo -e "  ${CYAN}--- Step 1: Camera Style ---${NC}"
echo ""
echo "    [1] Western Camera (RDR2 Style)"
echo "    [2] Immersive Close-Up"
echo "    [3] Cinematic Wide"
echo "    [4] Default (FoV Only)"
echo "    [5] Lower Camera (Witcher 3 Style)"
echo "    [6] Very Low Camera"
echo "    [7] Ultra Low Camera"
echo "    [8] RE2 Remake Style"
echo "    [R] Restore Original"
echo ""
echo -n "  Your choice: "
read -r STYLE_CHOICE

if [[ "${STYLE_CHOICE,,}" == "r" ]]; then
    PRESET="restore"
    if [[ ! -f "$PRESETS_DIR/restore.bin" ]]; then
        echo -e "  ${RED}restore.bin not found${NC}"
        exit 1
    fi
    BIN="$PRESETS_DIR/restore.bin"
    # Apply restore
    BINSIZE=$(stat -f%z "$BIN" 2>/dev/null || stat -c%s "$BIN" 2>/dev/null)
    if [[ "$BINSIZE" -ne "$EXPECTED_SIZE" ]]; then
        echo -e "  ${RED}ERROR: Wrong file size ($BINSIZE != $EXPECTED_SIZE)${NC}"
        exit 1
    fi
    dd if="$BIN" of="$PAZ" bs=1 seek=$PAZ_OFFSET conv=notrunc 2>/dev/null
    echo -e "  ${GREEN}Original camera restored!${NC}"
    exit 0
fi

case "$STYLE_CHOICE" in
    1) STYLE="western";;
    2) STYLE="immersive";;
    3) STYLE="cinematic";;
    4) STYLE="default";;
    5) STYLE="lowcam";;
    6) STYLE="vlowcam";;
    7) STYLE="ulowcam";;
    8) STYLE="re2";;
    *) echo -e "  ${RED}Invalid choice${NC}"; exit 1;;
esac

# ============================================================
# Step 2: Field of View
# ============================================================
echo -e "  ${CYAN}--- Step 2: Field of View ---${NC}"
echo ""
echo "    [0] Original (no change)"
echo "    [1] +10 degrees (subtle)"
echo "    [2] +15 degrees"
echo "    [3] +20 degrees (recommended)"
echo "    [4] +25 degrees"
echo "    [5] +30 degrees"
echo "    [6] +40 degrees (very wide)"
echo ""
echo -n "  Your choice: "
read -r FOV_CHOICE

case "$FOV_CHOICE" in
    0) FOVVAL="nofov";;
    1) FOVVAL="fov10";;
    2) FOVVAL="fov15";;
    3) FOVVAL="fov20";;
    4) FOVVAL="fov25";;
    5) FOVVAL="fov30";;
    6) FOVVAL="fov40";;
    *) echo -e "  ${RED}Invalid choice${NC}"; exit 1;;
esac

# ============================================================
# Step 3: Steadycam
# ============================================================
echo -e "  ${CYAN}--- Step 3: Steadycam ---${NC}"
echo "    Locks camera during movement, fixes horse camera."
echo -n "  Enable? (Y/n): "
read -r STEADY
STEADY="${STEADY:-Y}"

# ============================================================
# Step 4: Centered Camera (Bane Mode)
# ============================================================
echo -e "  ${CYAN}--- Step 4: Centered Camera (Bane Mode) ---${NC}"
echo "    Centers character on screen, removes shoulder offset."
echo -n "  Enable? (y/N): "
read -r BANE
BANE="${BANE:-N}"

# ============================================================
# Step 5: Combat Camera
# ============================================================
echo -e "  ${CYAN}--- Step 5: Combat Camera ---${NC}"
echo ""
echo "    [0] Default"
echo "    [1] Wider (+50%)"
echo "    [2] Maximum (+100%)"
echo ""
echo -n "  Your choice: "
read -r COMBAT_CHOICE

case "$COMBAT_CHOICE" in
    1) COMBAT="_cwide";;
    2) COMBAT="_cmax";;
    *) COMBAT="";;
esac

# ============================================================
# Build preset filename
# ============================================================
if [[ "$STYLE" == "default" ]]; then
    if [[ "${STEADY,,}" == "y" ]]; then
        PRESET="steady_${FOVVAL}"
    else
        PRESET="${FOVVAL}"
    fi
else
    PRESET="${STYLE}_${FOVVAL}"
fi

if [[ "${BANE,,}" == "y" ]]; then
    PRESET="${PRESET}_bane"
fi
PRESET="${PRESET}${COMBAT}"

BIN="$PRESETS_DIR/${PRESET}.bin"

if [[ ! -f "$BIN" ]]; then
    echo -e "  ${RED}Preset not available: ${PRESET}.bin${NC}"
    echo "  Try a different combination."
    exit 1
fi

# ============================================================
# Apply
# ============================================================
echo ""
echo -e "  Preset: ${BOLD}${PRESET}${NC}"

BINSIZE=$(stat -f%z "$BIN" 2>/dev/null || stat -c%s "$BIN" 2>/dev/null)
if [[ "$BINSIZE" -ne "$EXPECTED_SIZE" ]]; then
    echo -e "  ${RED}ERROR: Wrong file size ($BINSIZE != $EXPECTED_SIZE)${NC}"
    exit 1
fi

# Write preset to PAZ
dd if="$BIN" of="$PAZ" bs=1 seek=$PAZ_OFFSET conv=notrunc 2>/dev/null

echo ""
echo -e "  ${GREEN}============================================${NC}"
echo -e "  ${GREEN}  SUCCESS! '${PRESET}' installed!${NC}"
echo -e "  ${GREEN}============================================${NC}"
echo ""
echo "  Launch the game to see the changes."
echo "  To revert: run this installer and choose [R]"
echo ""
