#! /bin/bash

TYPE=$1
DEFAULT_GH_USER=linuxserver
DEFAULT_GH_REPO=proot-apps

if [[ ! -z ${LOCALREPO+x} ]] && [[ ! -z ${PA_REPO_FOLDER+x} ]]; then
  ROOT_DIR=$PA_REPO_FOLDER
else
  ROOT_DIR=$HOME/proot-apps
fi

# Check for deps
if [[ ! -f /usr/bin/curl ]]; then
  echo "Curl was not found on this system please install them to continue"
  exit 1
fi

#### Functions ####

# Get sha for image to download
get_blob_sha() {
  MULTIDIGEST=$(curl -s -f --retry 3 --retry-max-time 20 --retry-connrefused \
    --location \
    --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
    --header "Accept: application/vnd.oci.image.index.v1+json" \
    --header "Authorization: Bearer ${1}" \
    --user-agent "${UA}" \
    "${2}/${3}")
  if $HOME/.local/bin/jq -e '.layers // empty' <<< "${MULTIDIGEST}" >/dev/null 2>&1; then
    # If there's a layer element it's a single-arch manifest so just get that digest
    $HOME/.local/bin/jq -r '.layers[0].digest' <<< "${MULTIDIGEST}";
  else
    # Otherwise it's multi-arch or has manifest annotations
    if $HOME/.local/bin/jq -e '.manifests[]?.annotations // empty' <<< "${MULTIDIGEST}" >/dev/null 2>&1; then
      # Check for manifest annotations and delete if found
      MULTIDIGEST=$($HOME/.local/bin/jq 'del(.manifests[] | select(.annotations))' <<< "${MULTIDIGEST}")
    fi
    if [[ $($HOME/.local/bin/jq '.manifests | length' <<< "${MULTIDIGEST}") -gt 1 ]]; then
      # If there's still more than one digest, it's multi-arch
      MULTIDIGEST=$($HOME/.local/bin/jq -r ".manifests[] | select(.platform.architecture == \"${4}\").digest?" <<< "${MULTIDIGEST}")
      if [[ -z "${MULTIDIGEST}" ]]; then
        cleanup
      fi
    else
      # Otherwise it's single arch
      MULTIDIGEST=$($HOME/.local/bin/jq -r ".manifests[].digest?" <<< "${MULTIDIGEST}")
    fi
    if DIGEST=$(curl -s -f --retry 3 --retry-max-time 20 --retry-connrefused \
      --location \
      --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
      --header "Accept: application/vnd.oci.image.manifest.v1+json" \
      --header "Authorization: Bearer ${1}" \
      --user-agent "${UA}" \
      "${2}/${MULTIDIGEST}"); then
      $HOME/.local/bin/jq -r '.layers[0].digest' <<< "${DIGEST}";
    fi
  fi
}

# Trigger panel reload for selkies-desktop
function trigger_panel_reload() {
  if [ -f "$HOME/.config/panel-reload" ]; then
    touch "$HOME/.config/panel-reload"
  fi
}

# Get registry endpoint and auth
registry_setup() {
  IMAGE=$1
  # Determine endpoints and auth
  case "${IMAGE}" in
    ghcr.io/* )
    GHIMAGE=$(echo ${IMAGE} | sed 's|ghcr.io/||g')
    ENDPOINT="${GHIMAGE%%:*}"
    USERNAME="${GHIMAGE%%/*}"
    TAG="${GHIMAGE#*:}"
    REGISTRY="ghcr.io"
    AUTH_URL="https://ghcr.io/token?scope=repository%3A${ENDPOINT}%3Apull"
    ;;
  * )
    ENDPOINT="${IMAGE%%:*}"
    USERNAME="${IMAGE%%/*}"
    TAG="${IMAGE#*:}"
    REGISTRY="registry-1.docker.io"
    AUTH_URL="https://auth.docker.io/token?service=registry.docker.io&scope=repository:${ENDPOINT}:pull"
    ;;
  esac
  MANIFEST_URL="https://${REGISTRY}/v2/${ENDPOINT}/manifests"
  BLOB_URL="https://${REGISTRY}/v2/${ENDPOINT}/blobs/"
  TOKEN="$( 
    curl -s -f --retry 3 --retry-max-time 20 --retry-connrefused \
      "${AUTH_URL}" |
      $HOME/.local/bin/jq -r '.token'
  )"
}

# Download layer
function dl_layer() {
  mkdir -p $ROOT_DIR
  # CLI vars
  IMAGE=$1
  IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
  ARCH=$(uname -m| sed 's/x86_64/amd64/g'| sed 's/aarch64/arm64/')
  UA="Mozilla/5.0 (Linux $(uname -m)) kasmweb.com"

  # Destination directory
  mkdir -p "${ROOT_DIR}/${IMAGE_FOLDER}"
  touch "${ROOT_DIR}/${IMAGE_FOLDER}/DOWNLOADING"

  ## Functions ##

  # Cleanup and exit 1 if something went wrong
  cleanup() {
    rm -Rf "${ROOT_DIR}/${IMAGE_FOLDER}"
    exit 1
  }
  
  if [[ -z ${SHALAYER+x} ]]; then
    if [[ ! -z ${PA_REPO_FOLDER+x} ]] && [[ -z ${LOCALREPO+x} ]]; then
      SHALAYER=$(cat $PA_REPO_FOLDER/${IMAGE_FOLDER}/SHALAYER)
    else
      registry_setup ${IMAGE}
      SHALAYER=$(get_blob_sha "${TOKEN}" "${MANIFEST_URL}" "${TAG}" "${ARCH}")
    fi
  fi
  if [[ $? -eq 1 ]]; then
    echo "No manifest available for ${IMAGE}, cannot fetch"
    cleanup
  elif [[ -z "${SHALAYER}" ]]; then
    echo "${IMAGE} digest could not be fetched from ${REGISTRY}"
    cleanup
  fi

  # Download layer
  if [[ ! -z ${PA_REPO_FOLDER+x} ]] && [[ -z ${LOCALREPO+x} ]]; then
    echo "Extracting from local repo"
    tar -xf \
      $PA_REPO_FOLDER/${IMAGE_FOLDER}/app.tar.gz -C \
      "${ROOT_DIR}/${IMAGE_FOLDER}/"
  else
    if [[ ! -z ${LOCALREPO+x} ]] && [[ ! -z ${PA_REPO_FOLDER+x} ]]; then
      curl -f --retry 3 --retry-max-time 20 \
        -o ${ROOT_DIR}/${IMAGE_FOLDER}/app.tar.gz \
        --location \
        --header "Authorization: Bearer ${TOKEN}" \
        --user-agent "${UA}" \
        "${BLOB_URL}${SHALAYER}" 
    else
      curl -f --retry 3 --retry-max-time 20 \
        --location \
        --header "Authorization: Bearer ${TOKEN}" \
        --user-agent "${UA}" \
        "${BLOB_URL}${SHALAYER}" \
        | tar -xzf - -C "${ROOT_DIR}/${IMAGE_FOLDER}/"
    fi
    if [[ $? -ne 0 ]]; then
      echo "Error downloading ${IMAGE}"
      cleanup
    fi
  fi
  # Tag image
  echo "${SHALAYER}" > "${ROOT_DIR}/${IMAGE_FOLDER}/SHALAYER"

  # Cleanup
  rm -f "${ROOT_DIR}/${IMAGE_FOLDER}/DOWNLOADING"
}

# Update Icon cache
function update_icon_cache() {
  if [ ! -f "$HOME/.local/share/icons/hicolor/index.theme" ]; then
    mkdir -p $HOME/.local/share/icons/hicolor
    cp \
      /usr/share/icons/hicolor/index.theme \
      $HOME/.local/share/icons/hicolor/
  fi
  if which gtk-update-icon-cache >/dev/null 2>&1; then
    gtk-update-icon-cache $HOME/.local/share/icons/hicolor >/dev/null 2>&1 
  elif which update-icon-caches >/dev/null 2>&1; then
    update-icon-caches $HOME/.local/share/icons/hicolor >/dev/null 2>&1
  fi
}

# Run a unix socket to relay commands to the host
start_system_socket() {
  if ! pgrep -f ${1}system.socket; then
    rm -f ${1}system.socket
    $HOME/.local/bin/ncat -k -U -l ${1}system.socket | bash &
    BG_PIDS=$(jobs -p)
    chmod 600 ${1}system.socket || :
    tail --pid=$2 -f /dev/null
    kill -9 $BG_PIDS || :
  fi
}

# Run update
function update() {
  IMAGE_FOLDER=$1
  IMAGE=$(echo "${IMAGE_FOLDER}"| sed 's/\(.*\)_/\1:/' | sed 's|_|/|g')
  if [ ! -d "$ROOT_DIR/${IMAGE_FOLDER}/" ]; then
    echo "${IMAGE} not present on system run install or get first"
    exit 1
  fi
  LOCAL_SHA=$(cat "$ROOT_DIR/${IMAGE_FOLDER}/SHALAYER")
  if [[ ! -z ${PA_REPO_FOLDER+x} ]] && [[ -z ${LOCALREPO+x} ]]; then
    if [ ! -f $PA_REPO_FOLDER/${IMAGE_FOLDER}/SHALAYER ]; then
      echo "${IMAGE} is not available in local repo"
      exit 1
    fi
    # Use local SHA
    SHALAYER=$(cat $PA_REPO_FOLDER/${IMAGE_FOLDER}/SHALAYER)
  else
    # Check remote SHA
    ARCH=$(uname -m| sed 's/x86_64/amd64/g'| sed 's/aarch64/arm64/')
    UA="Mozilla/5.0 (Linux $(uname -m)) kasmweb.com"
    registry_setup ${IMAGE}
    SHALAYER=$(get_blob_sha "${TOKEN}" "${MANIFEST_URL}" "${TAG}" "${ARCH}")
  fi
  if [[ "${SHALAYER}" == "${LOCAL_SHA}" ]]; then
    echo "${IMAGE} is up to date: ${LOCAL_SHA}"
  else
    echo "Updating ${IMAGE_FOLDER}"
    # Run remove logic
    if [[ ! -z ${LOCALREPO+x} ]] && [[ ! -z ${PA_REPO_FOLDER+x} ]]; then
      echo "Removing app root local"
      rm -Rf $ROOT_DIR/${IMAGE_FOLDER}/
      dl_layer ${IMAGE}
    else
      if [ -f "$ROOT_DIR/${IMAGE_FOLDER}/remove" ]; then
        $HOME/.local/bin/proot \
          -R $ROOT_DIR/${IMAGE_FOLDER}/ \
          /remove
      fi
      # Remove app layer
      echo "Removing app root"
      rm -Rf $ROOT_DIR/${IMAGE_FOLDER}/
      dl_layer ${IMAGE}
      $HOME/.local/bin/proot \
        -R $ROOT_DIR/${IMAGE_FOLDER}/ \
        /install >/dev/null 2>&1
      # Refresh icon cache
      update_icon_cache
      # Install
      $HOME/.local/bin/proot \
        -R $ROOT_DIR/${IMAGE_FOLDER}/ \
        /install
    fi
    trigger_panel_reload
  fi
}

# Run remove
function remove() {
  IMAGE_FOLDER=$1
  IMAGE=$(echo "${IMAGE_FOLDER}"| sed 's/\(.*\)_/\1:/' | sed 's|_|/|g')
  echo "Removing ${IMAGE}"
  if [ ! -d "$ROOT_DIR/${IMAGE_FOLDER}/" ]; then
    echo "${IMAGE} not present on system run install or get first"
    exit 1
  fi
  if [[ ! -z ${LOCALREPO+x} ]] && [[ ! -z ${PA_REPO_FOLDER+x} ]]; then
    echo "localremove ${IMAGE}"
  else
    # Run remove logic
    if [ -f "$ROOT_DIR/${IMAGE_FOLDER}/remove" ]; then
      $HOME/.local/bin/proot \
        -R $ROOT_DIR/${IMAGE_FOLDER}/ \
        /remove
    fi
    # Remove bin wrapper if defined
    if [ -f "$ROOT_DIR/${IMAGE_FOLDER}/bin-name" ]; then
      rm -f $HOME/.local/bin/$(cat $ROOT_DIR/${IMAGE_FOLDER}/bin-name)
    fi
  fi
  # Remove app layer
  echo "Removing app root"
  rm -Rf $ROOT_DIR/${IMAGE_FOLDER}/
  trigger_panel_reload
}

# Run uninstall
function uninstall() {
  IMAGE_FOLDER=$1
  IMAGE=$(echo "${IMAGE_FOLDER}"| sed 's/\(.*\)_/\1:/' | sed 's|_|/|g')
  echo "Uninstalling ${IMAGE}"
  if [ ! -d "$ROOT_DIR/${IMAGE_FOLDER}/" ]; then
    echo "${IMAGE} not present on system run install or get first"
    exit 1
  fi
  # Run remove logic
  if [ -f "$ROOT_DIR/${IMAGE_FOLDER}/remove" ]; then
    $HOME/.local/bin/proot \
      -R $ROOT_DIR/${IMAGE_FOLDER}/ \
      /remove
  fi
  # Remove bin wrapper if defined
  if [ -f "$ROOT_DIR/${IMAGE_FOLDER}/bin-name" ]; then
    rm -f $HOME/.local/bin/$(cat $ROOT_DIR/${IMAGE_FOLDER}/bin-name)
  fi
  trigger_panel_reload
}

# Install
function install_app() {
  IMAGE=$1
  IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
  echo "Installing ${IMAGE}"
  # Download if not present
  if [ ! -d "$ROOT_DIR/${IMAGE_FOLDER}" ]; then
    dl_layer ${IMAGE}
  fi
  # Add bin wrapper if defined
  if [ -f "$ROOT_DIR/${IMAGE_FOLDER}/bin-name" ]; then
    echo "\$HOME/.local/bin/proot-apps run ${IMAGE} \"\$@\"" > $HOME/.local/bin/$(cat $ROOT_DIR/${IMAGE_FOLDER}/bin-name)
    chmod +x $HOME/.local/bin/$(cat $ROOT_DIR/${IMAGE_FOLDER}/bin-name)
    echo "$(cat $ROOT_DIR/${IMAGE_FOLDER}/bin-name) is now available from the command line"
  fi
  $HOME/.local/bin/proot \
    -R $ROOT_DIR/${IMAGE_FOLDER}/ \
    /install >/dev/null 2>&1
  # Refresh icon cache
  update_icon_cache
  # Install
  $HOME/.local/bin/proot \
    -R $ROOT_DIR/${IMAGE_FOLDER}/ \
    /install
  trigger_panel_reload
}

# Get
function get_app() {
  IMAGE=$1
  IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
  echo "Getting ${IMAGE}"
  # Download if not present
  if [ ! -d "$ROOT_DIR/${IMAGE_FOLDER}" ]; then
    dl_layer ${IMAGE}
  else
    echo "${IMAGE} present on this system use update to update"
  fi
}

# Print nvidia debug output if PA_DEBUG is set
nvidia_debug() {
  if [[ ! -z ${PA_DEBUG+x} ]]; then
    echo "nvidia: ${1}" >&2
  fi
}

# Print loader cache, ldconfig is in sbin which is not always in PATH
nvidia_ldcache() {
  for BIN in "$(which ldconfig 2>/dev/null)" /usr/sbin/ldconfig /sbin/ldconfig; do
    if [ -x "${BIN}" ]; then
      "${BIN}" -p 2>/dev/null
      return 0
    fi
  done
  for FILE in \
    "${HOST_LIB}"/lib*nvidia*.so* "${HOST_LIB}"/libcuda*.so* "${HOST_LIB}"/libnvcuvid.so* "${HOST_LIB}"/libnvoptix.so* \
    /usr/lib64/lib*nvidia*.so* /usr/lib64/libcuda*.so* /usr/lib64/libnvcuvid.so* /usr/lib64/libnvoptix.so* \
    /usr/lib/lib*nvidia*.so* /usr/lib/libcuda*.so* /usr/lib/libnvcuvid.so* /usr/lib/libnvoptix.so*; do
    if [ -e "${FILE}" ]; then
      echo "$(basename "${FILE}") (libc6,${LDTAG}) => ${FILE}"
    fi
  done
}

# Check for nvidia driver in the current environment
nvidia_present() {
  if [[ ! -z ${PA_NO_NVIDIA+x} ]]; then
    nvidia_debug "disabled by PA_NO_NVIDIA"
    return 1
  fi
  # Driver libs are glibc only
  if grep -q -E "^ID=alpine" /etc/os-release 2>/dev/null; then
    nvidia_debug "alpine environment, skipping"
    return 1
  fi
  if [ ! -e /lib64/ld-linux-x86-64.so.2 ] && [ ! -e /lib/ld-linux-aarch64.so.1 ]; then
    nvidia_debug "no glibc in this environment, skipping"
    return 1
  fi
  if [ ! -e /dev/nvidiactl ]; then
    return 1
  fi
  if ! nvidia_ldcache | grep -q -E "libGLX_nvidia\.so|libEGL_nvidia\.so"; then
    nvidia_debug "/dev/nvidiactl present but no driver libraries in the loader cache"
    return 1
  fi
  return 0
}

# Print guest glibc libdir, fails for musl guests
nvidia_guest_libdir() {
  if grep -q -E "^ID=alpine" "${1}/etc/os-release" 2>/dev/null; then
    nvidia_debug "alpine guest, skipping"
    return 1
  fi
  for DIR in \
    usr/lib/$(uname -m)-linux-gnu \
    usr/lib64 \
    usr/lib; do
    if [ -e "${1}/${DIR}/libc.so.6" ]; then
      echo "/${DIR}"
      return 0
    fi
  done
  nvidia_debug "no glibc found in guest, skipping"
  return 1
}

# Add a bind if the file exists and the guest path is not taken
nvidia_add() {
  if [ -f "${1}" ] && [ -z "${NVIDIA_SEEN[${2}]}" ]; then
    NVIDIA_SEEN[${2}]=1
    NVIDIA_BIND+=("-b" "$(realpath "${1}"):${2}")
  fi
}

# Build proot binds for nvidia acceleration
nvidia_binds() {
  NVIDIA_BIND=()
  declare -g -A NVIDIA_SEEN=()
  case "$(uname -m)" in
    x86_64) LDTAG="x86-64" ;;
    aarch64) LDTAG="AArch64" ;;
    *) return 0 ;;
  esac
  HOST_LIB="/usr/lib/$(uname -m)-linux-gnu"
  if ! nvidia_present; then
    return 0
  fi
  GUEST_LIB=$(nvidia_guest_libdir "${1}") || return 0
  # Driver libs
  while read -r NAME LIBPATH; do
    REAL=$(realpath "${LIBPATH}")
    nvidia_add "${REAL}" "${GUEST_LIB}/${NAME}"
    nvidia_add "${REAL}" "${GUEST_LIB}/$(basename "${REAL}")"
  done < <(nvidia_ldcache | awk -v tag="${LDTAG}" \
    '$1 ~ /^(libnvidia-|libcuda\.|libcudadebugger\.|libnvcuvid\.|libnvoptix\.|libEGL_nvidia\.|libGLX_nvidia\.|libGLESv1_CM_nvidia\.|libGLESv2_nvidia\.)/ && $1 !~ /^libnvidia-container/ && $2 ~ tag {print $1, $NF}')
  # VDPAU and GBM backends
  VDPAU_DIR=$(nvidia_ldcache | awk -v tag="${LDTAG}" \
    '$1 ~ /^libvdpau_nvidia\./ && $2 ~ tag {print $NF; exit}' | xargs -r realpath | xargs -r dirname)
  for FILE in \
    ${VDPAU_DIR:+"${VDPAU_DIR}"/libvdpau_nvidia.so*} \
    "${HOST_LIB}"/vdpau/libvdpau_nvidia.so* \
    /usr/lib64/vdpau/libvdpau_nvidia.so* \
    /usr/lib/vdpau/libvdpau_nvidia.so*; do
    nvidia_add "${FILE}" "${GUEST_LIB}/vdpau/$(basename "${FILE}")"
  done
  for FILE in \
    ${HOST_LIB}/gbm/nvidia-drm_gbm.so \
    /usr/lib64/gbm/nvidia-drm_gbm.so \
    /usr/lib/gbm/nvidia-drm_gbm.so \
    /usr/local/lib/gbm/nvidia-drm_gbm.so \
    /usr/local/lib64/gbm/nvidia-drm_gbm.so \
    ${HOST_LIB}/nvidia-drm_gbm.so \
    /usr/lib64/nvidia-drm_gbm.so \
    /usr/lib/nvidia-drm_gbm.so; do
    nvidia_add "${FILE}" "${GUEST_LIB}/gbm/nvidia-drm_gbm.so"
  done
  # GBM backend fallback to the allocator
  ALLOCATOR=$(nvidia_ldcache | awk -v tag="${LDTAG}" \
    '$1 ~ /^libnvidia-allocator\.so\.1$/ && $2 ~ tag {print $NF; exit}')
  if [ ! -z "${ALLOCATOR}" ]; then
    nvidia_add "${ALLOCATOR}" "${GUEST_LIB}/gbm/nvidia-drm_gbm.so"
  fi
  # ICD files
  STAGE="${ROOT_DIR}/.nvidia"
  ICD=$(find /etc/vulkan/icd.d /usr/share/vulkan/icd.d -name '*nvidia*.json' 2>/dev/null | head -n1)
  if [ -z "${ICD}" ]; then
    mkdir -p "${STAGE}"
    ICD="${STAGE}/nvidia_icd.json"
    echo '{"file_format_version":"1.0.0","ICD":{"library_path":"libGLX_nvidia.so.0","api_version":"1.3.0"}}' > "${ICD}"
  fi
  nvidia_add "${ICD}" /etc/vulkan/icd.d/nvidia_icd.json
  EGL=$(find /etc/glvnd/egl_vendor.d /usr/share/glvnd/egl_vendor.d -name '*nvidia*.json' 2>/dev/null | head -n1)
  if [ -z "${EGL}" ]; then
    mkdir -p "${STAGE}"
    EGL="${STAGE}/10_nvidia.json"
    echo '{"file_format_version":"1.0.0","ICD":{"library_path":"libEGL_nvidia.so.0"}}' > "${EGL}"
  fi
  nvidia_add "${EGL}" /usr/share/glvnd/egl_vendor.d/10_nvidia.json
  OCL=$(find /etc/OpenCL/vendors -name '*nvidia*.icd' 2>/dev/null | head -n1)
  if [ -z "${OCL}" ]; then
    mkdir -p "${STAGE}"
    OCL="${STAGE}/nvidia.icd"
    echo 'libnvidia-opencl.so.1' > "${OCL}"
  fi
  nvidia_add "${OCL}" /etc/OpenCL/vendors/nvidia.icd
  while read -r FILE; do
    nvidia_add "${FILE}" "/etc/vulkan/implicit_layer.d/$(basename "${FILE}")"
  done < <(find /etc/vulkan/implicit_layer.d /usr/share/vulkan/implicit_layer.d -name '*nvidia*.json' 2>/dev/null)
  while read -r FILE; do
    nvidia_add "${FILE}" "/usr/share/egl/egl_external_platform.d/$(basename "${FILE}")"
  done < <(find /etc/egl/egl_external_platform.d /usr/share/egl/egl_external_platform.d -name '*nvidia*.json' 2>/dev/null)
  # Tools
  if which nvidia-smi >/dev/null 2>&1; then
    nvidia_add "$(which nvidia-smi)" /usr/bin/nvidia-smi
  fi
  nvidia_debug "guest libdir ${GUEST_LIB}, $(( ${#NVIDIA_BIND[@]} / 2 )) binds"
  if [[ ! -z ${PA_DEBUG+x} ]]; then
    printf '%s\n' "${NVIDIA_BIND[@]}" | grep -v '^-b$' >&2
  fi
}

#### Runtime ####

# If a non docker endpoint is passed assume the default repo
if [[ ! -z ${2+x} ]]; then
  if ([[ "${2}" != *"/"* ]] || [[ "${2}" != *":"* ]]) && [[ "${2}" != "all" ]]; then
    IMAGE=ghcr.io/${DEFAULT_GH_USER}/${DEFAULT_GH_REPO}:${2}
  else
    IMAGE=${2}
  fi
fi

# Run the application
if [ "${TYPE}" == "run" ]; then
  if [[ -z ${2+x} ]]; then
    echo "The image is required"
    echo "Usage: proot-apps run myorg/myimage:tag"
    exit 1
  fi
  IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
  if [ ! -d "$ROOT_DIR/${IMAGE_FOLDER}/" ]; then
    echo "${IMAGE} not present on system run install or get first"
    exit 1
  fi
  # Docker shims for known desktop containers
  if [ -d "/defaults" ]; then
    PULSE_BIND="-b /defaults:/defaults"
  elif [ -d "/var/run/pulse" ]; then
    PULSE_BIND="-b /var/run/pulse:/var/run/pulse"
  fi
  # Mount repo location for the gui app
  if [[ ! -z ${PA_REPO_FOLDER+x} ]] && [[ "${IMAGE_FOLDER}" == *gui ]]; then
    REPO_BIND="-b ${PA_REPO_FOLDER}:${PA_REPO_FOLDER}"
    mkdir -p "$ROOT_DIR/${IMAGE_FOLDER}${PA_REPO_FOLDER}"
  fi
  # Fonts
  if [ -d "/usr/share/fonts" ] && [ -d "/usr/share/fontconfig" ] && [ -d "/etc/fonts" ]; then
    FONTS_BIND="-b /usr/share/fonts:/usr/share/fonts -b /usr/share/fontconfig:/usr/share/fontconfig -b /etc/fonts:/etc/fonts"
  fi
  # NVIDIA acceleration
  nvidia_binds "$ROOT_DIR/${IMAGE_FOLDER}"
  $HOME/.local/bin/proot \
    ${PULSE_BIND} ${FONTS_BIND} ${REPO_BIND} "${NVIDIA_BIND[@]}" \
    -R $ROOT_DIR/${IMAGE_FOLDER}/ \
    /entrypoint "${@:3}" &
  start_system_socket "$ROOT_DIR/${IMAGE_FOLDER}/" $!
fi

# Run installation
if [ "${TYPE}" == "install" ]; then
  if [[ -z ${2+x} ]]; then
    echo "The image is required"
    echo "Usage: proot-apps install myorg/myimage:tag"
    exit 1
  fi
  # Install multiple
  if [[ ! -z ${3+x} ]]; then
    for APP in "${@:2}"; do
      if [[ "${APP}" != *"/"* ]] || [[ "${APP}" != *":"* ]]; then
        install_app ghcr.io/${DEFAULT_GH_USER}/${DEFAULT_GH_REPO}:${APP}
      else
        install_app "${APP}"
      fi
      unset SHALAYER
    done
  # Install single or all
  else
    if [[ "${IMAGE}" != "all" ]]; then
      install_app "${IMAGE}"
    else
      if [ -n "$(ls -A $ROOT_DIR 2>/dev/null)" ]; then
        for IMAGE_FOLDER in $(ls $ROOT_DIR); do
          IMAGE=$(echo "${IMAGE_FOLDER}"| sed 's/\(.*\)_/\1:/' | sed 's|_|/|g')
          install_app "${IMAGE}"
        done
      else
        echo "no apps to install"
      fi
    fi
  fi
fi

# Run remove
if [ "${TYPE}" == "remove" ]; then
  if [[ -z ${2+x} ]]; then
    echo "The image is required"
    echo "Usage: proot-apps remove myorg/myimage:tag"
    exit 1
  fi
  # Remove multiple
  if [[ ! -z ${3+x} ]]; then
    for APP in "${@:2}"; do
      if [[ "${APP}" != *"/"* ]] || [[ "${APP}" != *":"* ]]; then
        IMAGE=ghcr.io/${DEFAULT_GH_USER}/${DEFAULT_GH_REPO}:${APP}
        IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
        remove "${IMAGE_FOLDER}"
      else
        IMAGE_FOLDER=$(echo "${APP}"| sed 's|/|_|g'| sed 's|:|_|g')
        remove "${IMAGE_FOLDER}"
      fi
    done
  # Uninstall single or all
  else
    if [[ "${IMAGE}" != "all" ]]; then
      IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
      remove "${IMAGE_FOLDER}"
    else
      if [ -n "$(ls -A $ROOT_DIR 2>/dev/null)" ]; then
        for IMAGE_FOLDER in $(ls $ROOT_DIR); do
          remove "${IMAGE_FOLDER}"
        done
      else
        echo "no apps to remove"
      fi
    fi
  fi
fi

# Run uninstall
if [ "${TYPE}" == "uninstall" ]; then
  if [[ -z ${2+x} ]]; then
    echo "The image is required"
    echo "Usage: proot-apps uninstall myorg/myimage:tag"
    exit 1
  fi
  # Uninstall multiple
  if [[ ! -z ${3+x} ]]; then
    for APP in "${@:2}"; do
      if [[ "${APP}" != *"/"* ]] || [[ "${APP}" != *":"* ]]; then 
        IMAGE=ghcr.io/${DEFAULT_GH_USER}/${DEFAULT_GH_REPO}:${APP}
        IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
        uninstall "${IMAGE_FOLDER}"
      else
        IMAGE_FOLDER=$(echo "${APP}"| sed 's|/|_|g'| sed 's|:|_|g')
        uninstall "${IMAGE_FOLDER}"
      fi
    done
  # Uninstall single or all
  else
    if [[ "${IMAGE}" != "all" ]]; then
      IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
      uninstall "${IMAGE_FOLDER}"
    else
      if [ -n "$(ls -A $ROOT_DIR 2>/dev/null)" ]; then
        for IMAGE_FOLDER in $(ls $ROOT_DIR); do
          uninstall "${IMAGE_FOLDER}"
        done
      else
        echo "no apps to uninstall"
      fi
    fi
  fi
fi

# Run update
if [ "${TYPE}" == "update" ]; then
  if [[ -z ${2+x} ]]; then
    echo "The image is required"
    echo "Usage: proot-apps update myorg/myimage:tag"
    exit 1
  fi
  # Update multiple
  if [[ ! -z ${3+x} ]]; then
    for APP in "${@:2}"; do
      if [[ "${APP}" != *"/"* ]] || [[ "${APP}" != *":"* ]]; then
        IMAGE=ghcr.io/${DEFAULT_GH_USER}/${DEFAULT_GH_REPO}:${APP}
        IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
        update "${IMAGE_FOLDER}"
      else
        IMAGE_FOLDER=$(echo "${APP}"| sed 's|/|_|g'| sed 's|:|_|g')
        update "${IMAGE_FOLDER}"
      fi
    done
  # Update single or all
  else
    if [[ "${IMAGE}" != "all" ]]; then
      IMAGE_FOLDER=$(echo "${IMAGE}"| sed 's|/|_|g'| sed 's|:|_|g')
      update "${IMAGE_FOLDER}"
    else
      if [ -n "$(ls -A $ROOT_DIR 2>/dev/null)" ]; then
        for IMAGE_FOLDER in $(ls $ROOT_DIR); do
          update "${IMAGE_FOLDER}"
        done
      else
        echo "no apps to update"
      fi
    fi
  fi
fi

# Run get
if [ "${TYPE}" == "get" ]; then
  if [[ -z ${2+x} ]]; then
    echo "The image is required"
    echo "Usage: proot-apps get myorg/myimage:tag"
    exit 1
  fi
  # Get multiple
  if [[ ! -z ${3+x} ]]; then
    for APP in "${@:2}"; do
      if [[ "${APP}" != *"/"* ]] || [[ "${APP}" != *":"* ]]; then
        IMAGE=ghcr.io/${DEFAULT_GH_USER}/${DEFAULT_GH_REPO}:${APP}
        get_app "${IMAGE}"
      else
        get_app "${IMAGE}"
      fi
      unset SHALAYER
    done
  # Get single
  else
    get_app "${IMAGE}"
  fi
fi

# Run localrepo actions
if [ "${TYPE}" == "localrepo" ]; then
  if [ -z ${PA_REPO_FOLDER+x} ]; then
    echo "error: PA_REPO_FOLDER is not set in env"
    exit 1
  fi
  if [[ -z ${2+x} ]] || [[ -z ${3+x} ]]; then
    echo "Usage: proot-apps localrepo [get,remove,update] myorg/myimage:tag"
    exit 1
  fi
  export LOCALREPO=true
  if [[ "${2}" =~ ^(get|remove|update)$ ]]; then
    "$(realpath "$0")" "${@:2}"
  fi
fi

# Usage
if [[ -z ${1+x} ]]; then
  echo "+-----------------------------------------------------------+"
  echo "| Usage:    | proot-apps [type] [image_name]                |"
  echo "+-----------------------------------------------------------+"
  echo "| Run       | proot-apps run myorg/myimage:tag              |"
  echo "| Install   | proot-apps install [myorg/myimage:tag|all]    |"
  echo "| Uninstall | proot-apps uninstall [myorg/myimage:tag|all]  |"
  echo "| Remove    | proot-apps remove [myorg/myimage:tag|all]     |"
  echo "| Get       | proot-apps get myorg/myimage:tag              |"
  echo "| Update    | proot-apps update [myorg/myimage:tag|all]     |"
  echo "+-----------------------------------------------------------+"
fi

#### Completion ####

# Convert an image folder to the name used on the command line
completion_name() {
  IMAGE_FOLDER=$1
  DEFAULT_PREFIX="ghcr.io_${DEFAULT_GH_USER}_${DEFAULT_GH_REPO}_"
  if [[ "${IMAGE_FOLDER}" == "${DEFAULT_PREFIX}"* ]]; then
    # Default repo apps use the short name
    echo "${IMAGE_FOLDER#"${DEFAULT_PREFIX}"}"
  else
    echo "${IMAGE_FOLDER}"| sed 's/\(.*\)_/\1:/' | sed 's|_|/|g'
  fi
}

# Only pass names that are safe to hand to the shell completion
completion_filter() {
  grep -E '^[A-Za-z0-9._/:-]+$'
}

# List app names from a folder of image folders
completion_folders() {
  APP_DIR=$1
  for IMAGE_FOLDER in $(ls ${APP_DIR} 2>/dev/null); do
    # Skip loose files and local repo metadata
    if [ ! -d "${APP_DIR}/${IMAGE_FOLDER}" ] || [ "${IMAGE_FOLDER}" == "metadata" ]; then
      continue
    fi
    completion_name "${IMAGE_FOLDER}"
  done | completion_filter
}

# List apps available to install
completion_apps() {
  # Local repo folder is the app list when set never reach out
  if [[ ! -z ${PA_REPO_FOLDER+x} ]]; then
    completion_folders "${PA_REPO_FOLDER}"
    return 0
  fi
  CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/proot-apps"
  CACHE_FILE="${CACHE_DIR}/completion-apps"
  # Ingest remote metadata on first use or when this script is newer than the cache
  if [ ! -s "${CACHE_FILE}" ] || [ "$(realpath "$0")" -nt "${CACHE_FILE}" ]; then
    ARCH=$(uname -m| sed 's/x86_64/amd64/g'| sed 's/aarch64/arm64/')
    META_URL="https://raw.githubusercontent.com/${DEFAULT_GH_USER}/${DEFAULT_GH_REPO}/master/metadata/metadata.yml"
    mkdir -p "${CACHE_DIR}"
    CACHE_TMP=$(mktemp "${CACHE_DIR}/completion-apps.XXXXXX")
    # Parse out app names skipping disabled apps and other arches
    curl -s -f --max-time 3 \
      --location \
      "${META_URL}" 2>/dev/null \
      | awk -v arch="linux/${ARCH}" '
        function flush() { if (n != "" && !d && (a == "" || index(a, arch))) print n }
        /^ *- name:/ { flush(); n=$3; d=0; a="" }
        /^ *arch:/ { a=$2 }
        /^ *disabled: *[Tt]rue/ { d=1 }
        END { flush() }' \
      | completion_filter > "${CACHE_TMP}"
    # Only a good list becomes the cache
    if [ -s "${CACHE_TMP}" ]; then
      mv -f "${CACHE_TMP}" "${CACHE_FILE}"
    else
      rm -f "${CACHE_TMP}"
      # Keep using a stale list without retrying on every tab
      if [ -s "${CACHE_FILE}" ]; then
        touch "${CACHE_FILE}"
      fi
    fi
  fi
  if [ ! -s "${CACHE_FILE}" ]; then
    return 1
  fi
  cat "${CACHE_FILE}"
}

# Print the bash completion script
completion_bash() {
  cat <<'EOF'
# bash completion for proot-apps
_proot_apps() {
  local cur words cword apps
  # Keep image names with a tag as one word when bash-completion is present
  if declare -F _get_comp_words_by_ref >/dev/null 2>&1; then
    _get_comp_words_by_ref -n : cur words cword
  else
    cur=${COMP_WORDS[COMP_CWORD]}
    words=("${COMP_WORDS[@]}")
    cword=${COMP_CWORD}
  fi
  COMPREPLY=()
  # Actions
  if [[ ${cword} -eq 1 ]]; then
    COMPREPLY=($(compgen -W "run install uninstall remove get update localrepo" -- "${cur}"))
    return
  fi
  case "${words[1]}" in
    get)
      apps=$("$1" _complete_apps 2>/dev/null) || return
      ;;
    install)
      apps=$("$1" _complete_apps 2>/dev/null) || return
      apps="all ${apps}"
      ;;
    remove|uninstall|update)
      apps=$("$1" _complete_installed 2>/dev/null) || return
      apps="all ${apps}"
      ;;
    run)
      # Anything after the app is passed to the app
      if [[ ${cword} -gt 2 ]]; then
        compopt -o filenames 2>/dev/null
        COMPREPLY=($(compgen -f -- "${cur}"))
        return
      fi
      apps=$("$1" _complete_installed 2>/dev/null) || return
      ;;
    localrepo)
      if [[ ${cword} -eq 2 ]]; then
        COMPREPLY=($(compgen -W "get remove update" -- "${cur}"))
      fi
      return
      ;;
    *)
      return
      ;;
  esac
  COMPREPLY=($(compgen -W "${apps}" -- "${cur}"))
  if declare -F __ltrim_colon_completions >/dev/null 2>&1; then
    __ltrim_colon_completions "${cur}"
  fi
}
complete -F _proot_apps proot-apps
EOF
}

# Print completion script
if [ "${TYPE}" == "completion" ]; then
  if [[ "${2}" != "bash" ]]; then
    echo "Usage: proot-apps completion bash"
    exit 1
  fi
  completion_bash
fi

# List available apps for completion
if [ "${TYPE}" == "_complete_apps" ]; then
  completion_apps || exit 1
fi

# List installed apps for completion
if [ "${TYPE}" == "_complete_installed" ]; then
  completion_folders "${ROOT_DIR}"
fi
