#!/usr/bin/env bash
#
# Train the no-extra-information T/O/S disease-history ablation.
#
# Fixed model:
#   TrajMixer + all_future + relative + Weibull
#
# History modes:
#   timed   (T): disease identities, order, and real first-onset times
#   ordered (O): disease identities and chronological order only
#   set     (S): unordered disease set only
#
# Jobs assigned to one GPU run sequentially. Different GPUs run in parallel.
#
# Examples:
#   bash train_disease_history_ablation_linux.sh --gpus 0,1,2
#   bash train_disease_history_ablation_linux.sh --gpus 0 --seeds 42 --modes ordered,set
#   bash train_disease_history_ablation_linux.sh --gpus 0,1 --dry-run
#

set -uo pipefail

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

GPU_CSV=""
SEED_CSV="42,43,44"
MODE_CSV="timed,ordered,set"
NUM_WORKERS=4
BATCH_SIZE=256
PYTHON_BIN="${PYTHON_BIN:-python}"
CAMPAIGN_NAME="disease_history_ablation_no_extra"
DRY_RUN=0

EXTRA_INFO_TYPES_FILE="$SCRIPT_DIR/extra_info_types_none.txt"
ENTRYPOINT="$SCRIPT_DIR/train_all_future.py"

usage() {
    cat <<'EOF'
Usage:
  bash train_disease_history_ablation_linux.sh --gpus GPU_LIST [options]

Required:
  --gpus LIST            Comma-separated GPU ids, for example 0,1,2.

Options:
  --seeds LIST           Comma-separated seeds (default: 42,43,44).
  --modes LIST           Subset of timed,ordered,set (default: all three).
  --batch-size N         Batch size per task (default: 256).
  --num-workers N        DataLoader workers per task (default: 4).
  --python PATH          Python executable (default: $PYTHON_BIN or python).
  --campaign NAME        Output campaign name.
  --dry-run              Print commands without creating files or training.
  -h, --help             Show this help message.

Fixed experiment settings:
  architecture           traj_mixer_v5
  target                 all_future
  time mode              relative
  distribution           weibull
  extra information      extra_info_types_none.txt

Outputs:
  runs/<campaign>/seed_<seed>/traj_mixer_v5/...
  batch_logs/<campaign>/seed_<seed>/<mode>.log
EOF
}

while (($# > 0)); do
    case "$1" in
        --gpus)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --gpus requires a value." >&2
                exit 2
            }
            GPU_CSV="$2"
            shift 2
            ;;
        --seeds)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --seeds requires a value." >&2
                exit 2
            }
            SEED_CSV="$2"
            shift 2
            ;;
        --modes)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --modes requires a value." >&2
                exit 2
            }
            MODE_CSV="$2"
            shift 2
            ;;
        --batch-size)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --batch-size requires a value." >&2
                exit 2
            }
            BATCH_SIZE="$2"
            shift 2
            ;;
        --num-workers)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --num-workers requires a value." >&2
                exit 2
            }
            NUM_WORKERS="$2"
            shift 2
            ;;
        --python)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --python requires a value." >&2
                exit 2
            }
            PYTHON_BIN="$2"
            shift 2
            ;;
        --campaign)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --campaign requires a value." >&2
                exit 2
            }
            CAMPAIGN_NAME="$2"
            shift 2
            ;;
        --dry-run)
            DRY_RUN=1
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            echo "ERROR: unknown argument: $1" >&2
            usage >&2
            exit 2
            ;;
    esac
done

[[ -n "$GPU_CSV" ]] || {
    echo "ERROR: --gpus is required." >&2
    usage >&2
    exit 2
}
[[ -n "$SEED_CSV" ]] || {
    echo "ERROR: --seeds must not be empty." >&2
    exit 2
}
[[ -n "$MODE_CSV" ]] || {
    echo "ERROR: --modes must not be empty." >&2
    exit 2
}
[[ "$BATCH_SIZE" =~ ^[1-9][0-9]*$ ]] || {
    echo "ERROR: --batch-size must be a positive integer." >&2
    exit 2
}
[[ "$NUM_WORKERS" =~ ^[0-9]+$ ]] || {
    echo "ERROR: --num-workers must be a non-negative integer." >&2
    exit 2
}
[[ "$CAMPAIGN_NAME" =~ ^[A-Za-z0-9._-]+$ ]] || {
    echo "ERROR: --campaign may contain only letters, numbers, ., _, and -." >&2
    exit 2
}
[[ -f "$EXTRA_INFO_TYPES_FILE" ]] || {
    echo "ERROR: missing extra-info file: $EXTRA_INFO_TYPES_FILE" >&2
    exit 2
}
[[ -f "$ENTRYPOINT" ]] || {
    echo "ERROR: missing training entrypoint: $ENTRYPOINT" >&2
    exit 2
}
command -v "$PYTHON_BIN" >/dev/null 2>&1 || {
    echo "ERROR: Python executable not found: $PYTHON_BIN" >&2
    exit 2
}

IFS=',' read -r -a GPU_IDS <<< "$GPU_CSV"
declare -A SEEN_GPUS=()
for gpu in "${GPU_IDS[@]}"; do
    [[ -n "$gpu" && "$gpu" =~ ^[A-Za-z0-9._:-]+$ ]] || {
        echo "ERROR: invalid GPU id: $gpu" >&2
        exit 2
    }
    [[ -z "${SEEN_GPUS[$gpu]+x}" ]] || {
        echo "ERROR: duplicate GPU id: $gpu" >&2
        exit 2
    }
    SEEN_GPUS["$gpu"]=1
done

IFS=',' read -r -a SEEDS <<< "$SEED_CSV"
declare -A SEEN_SEEDS=()
for seed in "${SEEDS[@]}"; do
    [[ "$seed" =~ ^[0-9]+$ ]] || {
        echo "ERROR: invalid seed: $seed" >&2
        exit 2
    }
    [[ -z "${SEEN_SEEDS[$seed]+x}" ]] || {
        echo "ERROR: duplicate seed: $seed" >&2
        exit 2
    }
    SEEN_SEEDS["$seed"]=1
done

IFS=',' read -r -a MODES <<< "$MODE_CSV"
declare -A SEEN_MODES=()
for mode in "${MODES[@]}"; do
    case "$mode" in
        timed|ordered|set)
            ;;
        *)
            echo "ERROR: invalid mode: $mode (expected timed, ordered, or set)." >&2
            exit 2
            ;;
    esac
    [[ -z "${SEEN_MODES[$mode]+x}" ]] || {
        echo "ERROR: duplicate mode: $mode" >&2
        exit 2
    }
    SEEN_MODES["$mode"]=1
done

RUNS_ROOT="$SCRIPT_DIR/runs/$CAMPAIGN_NAME"
LOG_ROOT="$SCRIPT_DIR/batch_logs/$CAMPAIGN_NAME"
if ((!DRY_RUN)); then
    mkdir -p "$RUNS_ROOT" "$LOG_ROOT"
fi

declare -a JOB_SEEDS=()
declare -a JOB_MODES=()
for seed in "${SEEDS[@]}"; do
    for mode in "${MODES[@]}"; do
        JOB_SEEDS+=("$seed")
        JOB_MODES+=("$mode")
    done
done

print_command() {
    printf '%q ' "$@"
    printf '\n'
}

run_job() {
    local job_index="$1"
    local gpu="$2"
    local seed="${JOB_SEEDS[$job_index]}"
    local mode="${JOB_MODES[$job_index]}"
    local seed_runs_root="$RUNS_ROOT/seed_$seed"
    local seed_log_root="$LOG_ROOT/seed_$seed"
    local log_file="$seed_log_root/$mode.log"
    local -a command=(
        "$PYTHON_BIN"
        -u
        "$ENTRYPOINT"
        --runs_root "$seed_runs_root"
        --seed "$seed"
        --batch_size "$BATCH_SIZE"
        --num_workers "$NUM_WORKERS"
        --device cuda
        --model_architecture traj_mixer_v5
        --time_mode relative
        --dist_mode weibull
        --extra_info_types_file "$EXTRA_INFO_TYPES_FILE"
        --disease_history_mode "$mode"
    )

    if ((!DRY_RUN)); then
        mkdir -p "$seed_runs_root" "$seed_log_root"
    fi

    echo "[$(date '+%F %T')] START seed=$seed mode=$mode gpu=$gpu"
    echo "  log=$log_file"
    if ((DRY_RUN)); then
        printf '  CUDA_VISIBLE_DEVICES=%q ' "$gpu"
        print_command "${command[@]}"
        return 0
    fi

    if CUDA_VISIBLE_DEVICES="$gpu" PYTHONUNBUFFERED=1 \
        "${command[@]}" >"$log_file" 2>&1; then
        echo "[$(date '+%F %T')] DONE  seed=$seed mode=$mode gpu=$gpu"
        return 0
    else
        local exit_code=$?
        echo "[$(date '+%F %T')] FAIL  seed=$seed mode=$mode gpu=$gpu exit=$exit_code" >&2
        echo "  See: $log_file" >&2
        return "$exit_code"
    fi
}

worker() {
    local slot="$1"
    local gpu="${GPU_IDS[$slot]}"
    local job_index
    local failed=0

    for ((job_index = slot; job_index < ${#JOB_SEEDS[@]}; job_index += ${#GPU_IDS[@]})); do
        run_job "$job_index" "$gpu" || failed=1
    done
    return "$failed"
}

echo "Campaign: $CAMPAIGN_NAME"
echo "Seeds: ${SEEDS[*]}"
echo "Modes: ${MODES[*]}"
echo "GPUs: ${GPU_IDS[*]}"
echo "Configurations per seed: ${#MODES[@]}"
echo "Total tasks: ${#JOB_SEEDS[@]}"
echo "Runs root: $RUNS_ROOT"
echo "Log root: $LOG_ROOT"
echo

declare -a WORKER_PIDS=()
for ((slot = 0; slot < ${#GPU_IDS[@]}; slot++)); do
    worker "$slot" &
    WORKER_PIDS+=("$!")
done

overall_status=0
for pid in "${WORKER_PIDS[@]}"; do
    wait "$pid" || overall_status=1
done

if ((overall_status != 0)); then
    echo "One or more training tasks failed. Inspect logs under: $LOG_ROOT" >&2
    exit 1
fi

if ((DRY_RUN)); then
    echo "Dry run completed successfully."
else
    echo "All disease-history ablation tasks completed successfully."
fi
