#!/usr/bin/env bash
set -euo pipefail

readonly app_root='/opt/private-keeper/app'
readonly wine_root='/opt/keeper-wine'
readonly package_keeper_executable="${app_root}/PrivateKeeper.exe"
readonly payload_manifest="${app_root}/manifest.sha256"
readonly wine_runtime_revision_file='/opt/private-keeper/wine-runtime.version'
readonly wine_executable="${wine_root}/bin/wine"
readonly wineboot_executable="${wine_root}/bin/wineboot"
readonly wineserver_executable="${wine_root}/bin/wineserver"
readonly wine_inf="${wine_root}/share/wine/wine.inf"
readonly legacy_prefix_preparation_contract='managed-wineserver-shutdown'
readonly prefix_preparation_contract='managed-wineserver-shutdown+wine-inf-mtime-v1'

show_error() {
  local message="$1"
  if [[ -x /usr/bin/zenity && ( -n "${DISPLAY-}" || -n "${WAYLAND_DISPLAY-}" ) ]]; then
    if ! /usr/bin/zenity --error --title='Private Keeper' --text="${message}" >/dev/null 2>&1; then
      printf 'Private Keeper: %s\n' "${message}" >&2
    fi
  else
    printf 'Private Keeper: %s\n' "${message}" >&2
  fi
  exit 1
}

[[ "$#" -eq 0 ]] || show_error 'Этот способ запуска не принимает параметры.'
[[ "$(id -u)" -ne 0 ]] || show_error 'Запускайте Private Keeper от обычного пользователя, не через sudo.'
[[ -n "${DISPLAY-}" || -n "${WAYLAND_DISPLAY-}" ]] || show_error 'Графический сеанс Linux недоступен.'
[[ -f "${package_keeper_executable}" ]] || show_error 'Файлы Private Keeper повреждены. Переустановите приложение.'
[[ -f "${payload_manifest}" && -x /usr/bin/sha256sum ]] || \
  show_error 'Проверка целостности Private Keeper недоступна. Переустановите приложение.'
[[ -x "${wine_executable}" && -x "${wineboot_executable}" && -x "${wineserver_executable}" ]] || \
  show_error 'Встроенная среда запуска повреждена. Переустановите приложение.'
[[ -f "${wine_inf}" && ! -L "${wine_inf}" ]] || \
  show_error 'Описание встроенной среды запуска повреждено. Переустановите приложение.'

[[ -f "${wine_runtime_revision_file}" && ! -L "${wine_runtime_revision_file}" ]] || \
  show_error 'Версия встроенной среды Private Keeper не определена. Переустановите приложение.'

mapfile -t wine_runtime_revision_lines < "${wine_runtime_revision_file}"
[[ "${#wine_runtime_revision_lines[@]}" -eq 1 && \
   "${wine_runtime_revision_lines[0]}" =~ ^[0-9A-Za-z][0-9A-Za-z.+:~-]*$ ]] || \
  show_error 'Версия встроенной среды Private Keeper повреждена. Переустановите приложение.'

if ! (cd -- "${app_root}" && /usr/bin/sha256sum --check --strict --quiet './manifest.sha256'); then
  show_error 'Файлы Private Keeper повреждены. Переустановите приложение.'
fi

if [[ -v XDG_DATA_HOME ]]; then
  [[ -n "${XDG_DATA_HOME}" && "${XDG_DATA_HOME}" == /* ]] || \
    show_error 'XDG_DATA_HOME должен быть абсолютным путём.'
  data_home="${XDG_DATA_HOME}"
else
  [[ -v HOME && -n "${HOME}" && "${HOME}" == /* ]] || \
    show_error 'Домашняя папка пользователя недоступна.'
  data_home="${HOME}/.local/share"
fi
readonly data_home
readonly keeper_data_root="${data_home}/private-keeper"
readonly native_bridge_root="${keeper_data_root}/native-bridge"
readonly wine_prefix="${keeper_data_root}/wineprefix"
readonly wine_runtime_revision_marker="${wine_prefix}/.private-keeper-wine-runtime"
readonly keeper_execution_root="${wine_prefix}/drive_c/PrivateKeeper"
readonly keeper_executable="${keeper_execution_root}/PrivateKeeper.exe"
readonly execution_payload_revision_marker="${keeper_execution_root}/.private-keeper-payload"

mkdir -p -- "${keeper_data_root}" || show_error 'Не удалось подготовить папку данных Private Keeper.'
chmod 700 -- "${keeper_data_root}" || show_error 'Не удалось защитить папку данных Private Keeper.'

if [[ -e "${native_bridge_root}" && \
      ( ! -d "${native_bridge_root}" || -L "${native_bridge_root}" ) ]]; then
  show_error 'Папка связи с системными компонентами Private Keeper повреждена.'
fi
mkdir -p -- "${native_bridge_root}" || \
  show_error 'Не удалось подготовить связь с системными компонентами Private Keeper.'
chmod 700 -- "${native_bridge_root}" || \
  show_error 'Не удалось защитить связь с системными компонентами Private Keeper.'
[[ "$(/usr/bin/stat --format='%u:%a' -- "${native_bridge_root}")" == \
   "$(id -u):700" ]] || \
  show_error 'Папка связи с системными компонентами Private Keeper имеет неверные права.'
export PRIVATE_KEEPER_NATIVE_BRIDGE_ROOT="${native_bridge_root}"

if ! exec 9>"${keeper_data_root}/prefix.lock"; then
  show_error 'Не удалось открыть блокировку среды Private Keeper.'
fi
/usr/bin/flock --wait 180 9 || show_error 'Другой запуск Private Keeper ещё подготавливает приложение.'

if [[ -e "${wine_prefix}" && ! -d "${wine_prefix}" ]]; then
  show_error 'Папка среды Private Keeper повреждена.'
fi

prefix_runtime_revision_matches=0
prefix_timestamp_migration_required=0
if [[ -f "${wine_runtime_revision_marker}" && ! -L "${wine_runtime_revision_marker}" ]]; then
  mapfile -t prefix_runtime_revision_lines < "${wine_runtime_revision_marker}"
  if [[ "${#prefix_runtime_revision_lines[@]}" -eq 2 && \
       "${prefix_runtime_revision_lines[0]}" == "${wine_runtime_revision_lines[0]}" ]]; then
    if [[ "${prefix_runtime_revision_lines[1]}" == "${prefix_preparation_contract}" ]]; then
      prefix_runtime_revision_matches=1
    elif [[ "${prefix_runtime_revision_lines[1]}" == \
        "${legacy_prefix_preparation_contract}" ]]; then
      prefix_runtime_revision_matches=1
      prefix_timestamp_migration_required=1
    fi
  fi
fi

prefix_update_required=0
prefix_marker_update_required=0
if [[ ! -f "${wine_prefix}/system.reg" ]]; then
  if [[ -d "${wine_prefix}" && -n "$(find "${wine_prefix}" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then
    show_error 'Незавершённая среда Private Keeper содержит посторонние файлы.'
  fi
  mkdir -p -- "${wine_prefix}" || show_error 'Не удалось создать среду Private Keeper.'
  prefix_update_required=1
  prefix_marker_update_required=1
elif [[ "${prefix_runtime_revision_matches}" -eq 0 ]]; then
  prefix_update_required=1
  prefix_marker_update_required=1
elif [[ "${prefix_timestamp_migration_required}" -eq 1 ]]; then
  prefix_marker_update_required=1
fi

shutdown_managed_wineserver() {
  local kill_status=0
  local wait_status=0

  /usr/bin/timeout --signal=TERM --kill-after=10s 60s \
    env -u DISPLAY -u WAYLAND_DISPLAY \
    WINEARCH=win64 WINEPREFIX="${wine_prefix}" WINEDEBUG=-all \
    "${wineserver_executable}" --kill >/dev/null 2>&1 || kill_status=$?
  /usr/bin/timeout --signal=TERM --kill-after=10s 60s \
    env -u DISPLAY -u WAYLAND_DISPLAY \
    WINEARCH=win64 WINEPREFIX="${wine_prefix}" WINEDEBUG=-all \
    "${wineserver_executable}" --wait >/dev/null 2>&1 || wait_status=$?

  [[ ( "${kill_status}" -eq 0 || "${kill_status}" -eq 1 ) && \
     "${wait_status}" -eq 0 ]]
}

if [[ "${prefix_timestamp_migration_required}" -eq 1 ]]; then
  [[ -s "${wine_prefix}/system.reg" && -s "${wine_prefix}/user.reg" && \
     -s "${wine_prefix}/userdef.reg" ]] || \
    show_error 'Существующая среда Private Keeper не завершена.'
  grep -Fq '#arch=win64' "${wine_prefix}/system.reg" || \
    show_error 'Среда Private Keeper имеет неверную архитектуру.'
  [[ ! -L "${wine_prefix}/.update-timestamp" && \
     ( ! -e "${wine_prefix}/.update-timestamp" || \
       -f "${wine_prefix}/.update-timestamp" ) ]] || \
    show_error 'Метка обновления среды Private Keeper повреждена.'

  shutdown_managed_wineserver || \
    show_error 'Не удалось остановить незавершённое обновление среды Private Keeper.'

  wine_inf_timestamp="$(/usr/bin/stat --format='%Y' -- "${wine_inf}")"
  [[ "${wine_inf_timestamp}" =~ ^[1-9][0-9]*$ ]] || \
    show_error 'Время версии встроенной среды Private Keeper повреждено.'
  wine_update_timestamp_pending="${wine_prefix}/.update-timestamp.pending"
  [[ ! -e "${wine_update_timestamp_pending}" && \
     ! -L "${wine_update_timestamp_pending}" ]] || \
    show_error 'Временная метка обновления среды Private Keeper уже существует.'
  printf '%s\n' "${wine_inf_timestamp}" > "${wine_update_timestamp_pending}" || \
    show_error 'Не удалось обновить метку среды Private Keeper.'
  chmod 600 -- "${wine_update_timestamp_pending}" || \
    show_error 'Не удалось защитить метку среды Private Keeper.'
  mv -f -- "${wine_update_timestamp_pending}" \
    "${wine_prefix}/.update-timestamp" || \
    show_error 'Не удалось завершить миграцию среды Private Keeper.'
fi

prepare_wine_prefix() (
  local managed_wineserver_started=0

  cleanup_managed_wineserver_on_exit() {
    local status="$1"
    trap - EXIT
    if [[ "${managed_wineserver_started}" -eq 1 ]] && ! shutdown_managed_wineserver; then
      status=1
    fi
    exit "${status}"
  }
  trap 'cleanup_managed_wineserver_on_exit "$?"' EXIT

  printf '5\n# Подготовка Private Keeper к запуску…\n'
  /usr/bin/timeout --signal=TERM --kill-after=10s 30s \
    env -u DISPLAY -u WAYLAND_DISPLAY \
    WINEARCH=win64 WINEPREFIX="${wine_prefix}" WINEDEBUG=-all \
    "${wineserver_executable}" --persistent >/dev/null 2>&1
  managed_wineserver_started=1
  printf '15\n# Настройка встроенной среды…\n'
  /usr/bin/timeout --signal=TERM --kill-after=10s 180s \
    env -u DISPLAY -u WAYLAND_DISPLAY \
    WINEARCH=win64 WINEPREFIX="${wine_prefix}" WINEDEBUG=-all \
    WINEDLLOVERRIDES='mscoree,mshtml,winemenubuilder.exe=d' \
    "${wineboot_executable}" -u >/dev/null 2>&1
  printf '90\n# Завершение настройки…\n'
  shutdown_managed_wineserver
  managed_wineserver_started=0
  printf '100\n'
)

if [[ "${prefix_update_required}" -eq 1 ]]; then
  if ! prepare_wine_prefix | /usr/bin/zenity \
      --progress \
      --pulsate \
      --auto-close \
      --no-cancel \
      --title='Private Keeper' \
      --text='Подготовка Private Keeper к запуску…'; then
    show_error 'Не удалось подготовить Private Keeper. Переустановите приложение или передайте ошибку поддержке.'
  fi

  [[ -s "${wine_prefix}/system.reg" && -s "${wine_prefix}/user.reg" && \
     -s "${wine_prefix}/userdef.reg" ]] || \
    show_error 'Среда Private Keeper не была подготовлена.'
fi

grep -Fq '#arch=win64' "${wine_prefix}/system.reg" || show_error 'Среда Private Keeper имеет неверную архитектуру.'

payload_revision="$(/usr/bin/sha256sum -- "${payload_manifest}")"
payload_revision="${payload_revision%% *}"
[[ "${payload_revision}" =~ ^[0-9a-f]{64}$ ]] || \
  show_error 'Индекс файлов Private Keeper повреждён. Переустановите приложение.'

execution_payload_update_required=1
if [[ -f "${execution_payload_revision_marker}" && \
     ! -L "${execution_payload_revision_marker}" ]]; then
  mapfile -t execution_payload_revision_lines < \
    "${execution_payload_revision_marker}"
  if [[ "${#execution_payload_revision_lines[@]}" -eq 1 && \
       "${execution_payload_revision_lines[0]}" == "${payload_revision}" ]] && \
     (cd -- "${keeper_execution_root}" && \
       /usr/bin/sha256sum --check --strict --quiet './manifest.sha256'); then
    execution_payload_update_required=0
  fi
fi

if [[ "${execution_payload_update_required}" -eq 1 ]]; then
  if [[ -e "${keeper_execution_root}" && \
       ( ! -d "${keeper_execution_root}" || -L "${keeper_execution_root}" ) ]]; then
    show_error 'Папка приложения Private Keeper повреждена.'
  fi
  mkdir -p -- "${keeper_execution_root}" || \
    show_error 'Не удалось подготовить файлы Private Keeper.'
  if find "${keeper_execution_root}" -xdev -type l -print -quit | grep -q .; then
    show_error 'Папка приложения Private Keeper содержит недопустимые ссылки.'
  fi
  /usr/bin/cp -R --no-preserve=ownership -- "${app_root}/." \
    "${keeper_execution_root}/" || \
    show_error 'Не удалось подготовить файлы Private Keeper.'
  if ! (cd -- "${keeper_execution_root}" && \
      /usr/bin/sha256sum --check --strict --quiet './manifest.sha256'); then
    show_error 'Рабочие файлы Private Keeper повреждены.'
  fi
  execution_payload_revision_pending="${execution_payload_revision_marker}.pending"
  printf '%s\n' "${payload_revision}" > \
    "${execution_payload_revision_pending}" || \
    show_error 'Не удалось записать версию файлов Private Keeper.'
  chmod 600 -- "${execution_payload_revision_pending}" || \
    show_error 'Не удалось защитить версию файлов Private Keeper.'
  mv -f -- "${execution_payload_revision_pending}" \
    "${execution_payload_revision_marker}" || \
    show_error 'Не удалось завершить подготовку файлов Private Keeper.'
fi

[[ -f "${keeper_executable}" && ! -L "${keeper_executable}" ]] || \
  show_error 'Рабочие файлы Private Keeper повреждены.'

if [[ "${prefix_marker_update_required}" -eq 1 ]]; then
  wine_runtime_revision_pending="${wine_runtime_revision_marker}.pending"
  /usr/bin/install --mode=0600 -- "${wine_runtime_revision_file}" \
    "${wine_runtime_revision_pending}" || \
    show_error 'Не удалось записать версию среды Private Keeper.'
  printf '%s\n' "${prefix_preparation_contract}" >> "${wine_runtime_revision_pending}" || \
    show_error 'Не удалось записать контракт подготовки среды Private Keeper.'
  mv -f -- "${wine_runtime_revision_pending}" "${wine_runtime_revision_marker}" || \
    show_error 'Не удалось завершить обновление среды Private Keeper.'
fi
/usr/bin/flock --unlock 9 || show_error 'Не удалось завершить подготовку среды Private Keeper.'
exec 9>&-

cd -- "${keeper_execution_root}"
exec env \
  WINEARCH=win64 \
  WINEPREFIX="${wine_prefix}" \
  WINEDEBUG=-all \
  WINEDLLOVERRIDES='mscoree,mshtml,winemenubuilder.exe=d' \
  "${wine_executable}" './PrivateKeeper.exe'
