#!/usr/bin/env python3

# This wrapper script facilitates setup and use of SynthMorph containers by
# pulling them from the Docker Hub and mounting the host directory defined by
# environment variable SUBJECTS_DIR to /mnt in the container. Invoke the script
# just like `mri_synthmorph` in FreeSurfer, with one exception: you can only
# read and write data under SUBJECTS_DIR, which will be the working directory
# in the container. If unset, SUBJECTS_DIR defaults to your current directory.
# This means you can access relative paths under your working directory without
# setting SUBJECTS_DIR. In other words, SUBJECTS_DIR sets the working directory
# for SynthMorph, and you can specify paths relative to it.

# Update the version to pull a different image, unless you already have it.
version = 4

# Local image location for Apptainer/Singularity. Set an absolute path to avoid
# pulling new images when you change the folder. Ignored for Docker and Podman.
sif_file = f'synthmorph_{version}.sif'

# We will use the first of the below container systems found in your PATH, from
# left to right. You may wish to reorder them, If you have several installed.
tools = ('docker', 'apptainer', 'singularity', 'podman')


import os
import sys
import signal
import shutil
import subprocess


# Report version. Avoid errors when piping, for example to `head`.
signal.signal(signal.SIGPIPE, handler=signal.SIG_DFL)
hub = 'https://hub.docker.com/u/freesurfer'
print(f'Running SynthMorph version {version} from {hub}')


# Find a container system.
for tool in tools:
    path = shutil.which(tool)
    if path:
        print(f'Using {path} to manage containers')
        break

if not path:
    print(f'Cannot find container tools {tools} in PATH', file=sys.stderr)
    exit(1)


# Prepare bind path and URL. Mount SUBJECTS_DIR as /mnt inside the container,
# which we made the working directory when building the image. While Docker
# and Podman will respect it, they require absolute paths for bind mounts.
host = os.environ.get('SUBJECTS_DIR', os.getcwd())
host = os.path.abspath(host)
print(f'Will bind /mnt in image to SUBJECTS_DIR="{host}"')

image = f'freesurfer/synthmorph:{version}'
if tool != 'docker':
    image = f'docker://{image}'


# Run Docker containers with the UID and GID of the host user. This user will
# own bind mounts inside the container, preventing output files owned by root.
# Root inside a rootless Podman container maps to the non-root host user, which
# is what we want. If we set UID and GID inside the container to the non-root
# host user as we do for Docker, then these would get remapped according to
# /etc/subuid outside, causing problems with read and write permissions.
if tool in ('docker', 'podman'):
    arg = ('run', '--rm', '-v', f'{host}:/mnt')

    # Pretty-print help text.
    if sys.stdout.isatty():
        arg = (*arg, '-t')
    if tool == 'docker':
        arg = (*arg, '-u', f'{os.getuid()}:{os.getgid()}')

    arg = (*arg, image)


# For Apptainer/Singularity, the user inside and outside the container is the
# same. The working directory is also the same, unless we explicitly set it.
if tool in ('apptainer', 'singularity'):
    arg = ('run', '--nv', '--pwd', '/mnt', '-e', '-B', f'{host}:/mnt', sif_file)

    if not os.path.isfile(sif_file):
        print(f'Cannot find image {sif_file}, pulling it', file=sys.stderr)
        proc = subprocess.run((tool, 'pull', sif_file, image))
        if proc.returncode:
            exit(proc.returncode)


# Summarize and launch container.
print('Command:', ' '.join((tool, *arg)))
print('SynthMorph arguments:', *sys.argv[1:])
proc = subprocess.run((tool, *arg, *sys.argv[1:]))
exit(proc.returncode)
