#!/usr/bin/env bash

# For maximum AppImage compatibility, build on the oldest Linux distribution
# that still receives security updates from its manufacturer.

set -e # Exit on errors
set -x # Be verbose

# Go one-up from MuseScore root dir regardless of where script was run from:
cd "$(dirname "$(readlink -f "${0}")")/../../../.."

##########################################################################
# GET DEPENDENCIES
##########################################################################

# DISTRIBUTION PACKAGES

# These are installed by default on Travis CI, but not on Docker
apt_packages_basic=(
  # Alphabetical order please!
  file
  git
  pkg-config
  software-properties-common # installs `add-apt-repository`
  unzip
  )

# These are the same as on Travis CI
apt_packages_standard=(
  # Alphabetical order please!
  # cmake # using more recent upstream version from cmake.org
  curl
  libasound2-dev # ALSA
  libfontconfig1-dev
  libfreetype6-dev
  libgl1-mesa-dev
  libjack-dev
  libmp3lame-dev
  libnss3-dev
  libportmidi-dev
  libpulse-dev
  libsndfile1-dev
  make
  portaudio19-dev
  wget
  )

# MuseScore compiles without these but won't run without them
apt_packages_runtime=(
  # Alphabetical order please!
  libcups2
  libdbus-1-3
  libegl1-mesa-dev
  libodbc1
  libpq-dev
  libssl1.0.0
  libxcomposite-dev
  libxcursor-dev
  libxi-dev
  libxkbcommon-x11-0
  libxrandr2
  libxtst-dev
  )

apt-get update # no package lists in Docker image
apt-get install -y --no-install-recommends \
  "${apt_packages_basic[@]}" \
  "${apt_packages_standard[@]}" \
  "${apt_packages_runtime[@]}"

# NON-DISTRIBUTION PACKAGES
# We use these when the default version provided by the distribution is
# too old or has unwelcome modifications compared to the upstream version.

# Get newer compiler
gcc_version="4.9"
if [[ ! -f /etc/apt/sources.list.d/ubuntu-toolchain-r-test-*.list ]]; then
  add-apt-repository -y ppa:ubuntu-toolchain-r/test
  apt-get update
fi
apt-get install -y --no-install-recommends "g++-${gcc_version}"
update-alternatives \
  --install /usr/bin/gcc gcc "/usr/bin/gcc-${gcc_version}" 40 \
  --slave /usr/bin/g++ g++ "/usr/bin/g++-${gcc_version}"
export CC="/usr/bin/gcc-${gcc_version}"
export CXX="/usr/bin/g++-${gcc_version}"

# Get newer Qt (only used cached version if it is the same)
qt_version="598"
qt_dir="qt/${qt_version}"
if [[ ! -d "${qt_dir}" ]]; then
  mkdir -p "${qt_dir}"
  qt_url="https://s3.amazonaws.com/utils.musescore.org/qt${qt_version}.zip"
  wget -q --show-progress -O qt5.zip "${qt_url}"
  unzip -qq qt5.zip -d "${qt_dir}"
  rm -f qt5.zip
fi
qt_path="${PWD%/}/${qt_dir}"
export PATH="${qt_path}/bin:${PATH}"
export LD_LIBRARY_PATH="${qt_path}/lib:${LD_LIBRARY_PATH}"
export QT_PLUGIN_PATH="${qt_path}/plugins"
export QML2_IMPORT_PATH="${qt_path}/qml"

# Get newer CMake (only used cached version if it is the same)
cmake_version="3.10.1"
cmake_dir="cmake/${cmake_version}"
if [[ ! -d "${cmake_dir}" ]]; then
  mkdir -p "${cmake_dir}"
  cmake_url="https://cmake.org/files/v${cmake_version%.*}/cmake-${cmake_version}-Linux-x86_64.tar.gz"
  wget -q --show-progress --no-check-certificate -O - "${cmake_url}" \
    | tar --strip-components=1 -xz -C "${cmake_dir}"
fi
export PATH="${PWD%/}/${cmake_dir}/bin:${PATH}"

# Build MuseScore on Travis but not on Docker Cloud (reduce container size)
if [[ ! -d "MuseScore" ]]; then
  # tidy up (reduce size of Docker image)
  apt-get clean autoclean
  apt-get autoremove --purge -y
  rm -rf /tmp/* /var/{cache,log,backups}/* /var/lib/apt/*
  exit 0
fi

##########################################################################
# BUILD MUSESCORE
##########################################################################

cd MuseScore
make revision
make "$@" portable
cd ..

##########################################################################
# GET APPIMAGETOOL AND LINUXDEPLOY
##########################################################################

function extract_appimage()
{
  # Extract AppImage so we can run it without having to install FUSE
  local -r appimage="$1" binary_name="$2"
  local -r appdir="${appimage%.AppImage}.AppDir"
  "./${appimage}" --appimage-extract >/dev/null # dest folder "squashfs-root"
  mv squashfs-root "${appdir}" # rename folder to avoid collisions
  ln -s "${appdir}/AppRun" "${binary_name}" # symlink for convenience
  rm -f "${appimage}"
}

function download_github_release()
{
  local -r repo_slug="$1" release_tag="$2" file="$3"
  wget -q --show-progress \
    "https://github.com/${repo_slug}/releases/download/${release_tag}/${file}"
  chmod +x "${file}"
}

function download_appimage_release()
{
  local -r github_repo_slug="$1" binary_name="$2" tag="$3"
  local -r appimage="${binary_name}-x86_64.AppImage"
  download_github_release "${github_repo_slug}" "${tag}" "${appimage}"
  extract_appimage "${appimage}" "${binary_name}"
}

function download_linuxdeploy_component()
{
  download_appimage_release "linuxdeploy/$1" "$1" continuous
}

# GET LATEST APPIMAGETOOL AND LINUXDEPLOY
# We didn't do this earlier because we don't want them to get cached in the
# Docker image. We always want the latest versions with the latest features.
# We still check for an existing version to avoid re-downloading if the
# script is run again in the same container (e.g. during local development).

if [[ ! -d "appimagetool" ]]; then
  mkdir appimagetool
  cd appimagetool
  # `12` and not `continuous` because see https://github.com/AppImage/AppImageKit/issues/1060
  download_appimage_release AppImage/AppImageKit appimagetool 12
  cd ..
fi
export PATH="${PWD%/}/appimagetool:$PATH"
appimagetool --version

if [[ ! -d "linuxdeploy" ]]; then
  mkdir linuxdeploy
  cd linuxdeploy
  download_linuxdeploy_component linuxdeploy
  download_linuxdeploy_component linuxdeploy-plugin-qt
  cd ..
fi
export PATH="${PWD%/}/linuxdeploy:$PATH"
linuxdeploy --list-plugins

##########################################################################
# BUNDLE DEPENDENCIES INTO APPDIR
##########################################################################

cd MuseScore
prefix="$(cat build.release/PREFIX.txt)" # MuseScore was installed here
cd "$(dirname "${prefix}")"
appdir="$(basename "${prefix}")" # directory that will become the AppImage

# Prevent linuxdeploy setting RUNPATH in binaries that shouldn't have it
mv "${appdir}/bin/findlib" "${appdir}/../findlib"

# Remove Qt plugins for MySQL and PostgreSQL to prevent
# linuxdeploy-plugin-qt from failing due to missing dependencies.
# SQLite plugin alone should be enough for our AppImage.
# rm -f ${qt_path}/plugins/sqldrivers/libqsql{mysql,psql}.so
qt_sql_drivers_path="${qt_path}/plugins/sqldrivers"
qt_sql_drivers_tmp="/tmp/qtsqldrivers"
mkdir -p "$qt_sql_drivers_tmp"
mv "${qt_sql_drivers_path}/libqsqlmysql.so" "${qt_sql_drivers_tmp}/libqsqlmysql.so"
mv "${qt_sql_drivers_path}/libqsqlpsql.so" "${qt_sql_drivers_tmp}/libqsqlpsql.so"

# Colon-separated list of root directories containing QML files.
# Needed for linuxdeploy-plugin-qt to scan for QML imports.
export QML_SOURCES_PATHS=/MuseScore/mscore/qml/palettes

linuxdeploy --appdir "${appdir}" # adds all shared library dependencies
linuxdeploy-plugin-qt --appdir "${appdir}" # adds all Qt dependencies

unset QML_SOURCES_PATHS

# In case this container is reused multiple times, return the moved libraries back
mv "${qt_sql_drivers_tmp}/libqsqlmysql.so" "${qt_sql_drivers_path}/libqsqlmysql.so"
mv "${qt_sql_drivers_tmp}/libqsqlpsql.so" "${qt_sql_drivers_path}/libqsqlpsql.so"

# Put the non-RUNPATH binaries back
mv "${appdir}/../findlib" "${appdir}/bin/findlib"

##########################################################################
# BUNDLE REMAINING DEPENDENCIES MANUALLY
##########################################################################

function find_library()
{
  # Print full path to a library or return exit status 1 if not found
  "${appdir}/bin/findlib" "$@"
}

function fallback_library()
{
  # Copy a library into a special fallback directory inside the AppDir.
  # Fallback libraries are not loaded at runtime by default, but they can
  # be loaded if it is found that the application would crash otherwise.
  local library="$1"
  local full_path="$(find_library "$1")"
  local new_path="${appdir}/fallback/${library}"
  mkdir -p "${new_path}" # directory has the same name as the library
  cp -L "${full_path}" "${new_path}/${library}"
  # Use the AppRun script to check at runtime whether the user has a copy of
  # this library. If not then add our copy's directory to $LD_LIBRARY_PATH.
}

# UNWANTED FILES
# linuxdeploy or linuxdeploy-plugin-qt may have added some files or folders
# that we don't want. List them here using paths relative to AppDir root.
# Report new additions at https://github.com/linuxdeploy/linuxdeploy/issues
# or https://github.com/linuxdeploy/linuxdeploy-plugin-qt/issues for Qt libs.
unwanted_files=(
  # none
)

# ADDITIONAL QT COMPONENTS
# linuxdeploy-plugin-qt may have missed some Qt files or folders that we need.
# List them here using paths relative to the Qt root directory. Report new
# additions at https://github.com/linuxdeploy/linuxdeploy-plugin-qt/issues
additional_qt_components=(
  /plugins/printsupport/libcupsprintersupport.so
)

# ADDITIONAL LIBRARIES
# linuxdeploy may have missed some libraries that we need
# Report new additions at https://github.com/linuxdeploy/linuxdeploy/issues
additional_libraries=(
  libssl.so.1.0.0    # OpenSSL (for Save Online)
  libcrypto.so.1.0.0 # OpenSSL (for Save Online)
)

# FALLBACK LIBRARIES
# These get bundled in the AppImage, but are only loaded if the user does not
# already have a version of the library installed on their system. This is
# helpful in cases where it is necessary to use a system library in order for
# a particular feature to work properly, but where the program would crash at
# startup if the library was not found. The fallback library may not provide
# the full functionality of the system version, but it does avoid the crash.
# Report new additions at https://github.com/linuxdeploy/linuxdeploy/issues
fallback_libraries=(
  libjack.so.0 # https://github.com/LMMS/lmms/pull/3958
)

for file in "${unwanted_files[@]}"; do
  rm -rf "${appdir}/${file}"
done

for file in "${additional_qt_components[@]}"; do
  mkdir -p "${appdir}/$(dirname "${file}")"
  cp -L "${qt_path}/${file}" "${appdir}/${file}"
done

for lib in "${additional_libraries[@]}"; do
  full_path="$(find_library "${lib}")"
  cp -L "${full_path}" "${appdir}/lib/${lib}"
done

for fb_lib in "${fallback_libraries[@]}"; do
  fallback_library "${fb_lib}"
done

# METHOD OF LAST RESORT
# Special treatment for some dependencies when all other methods fail

# Bundle libnss3 and friends as fallback libraries. Needed on Chromebook.
# See discussion at https://github.com/probonopd/linuxdeployqt/issues/35
libnss3_files=(
  # https://packages.ubuntu.com/xenial/amd64/libnss3/filelist
  libnss3.so
  libnssutil3.so
  libsmime3.so
  libssl3.so
  nss/libfreebl3.chk
  nss/libfreebl3.so
  nss/libfreeblpriv3.chk
  nss/libfreeblpriv3.so
  nss/libnssckbi.so
  nss/libnssdbm3.chk
  nss/libnssdbm3.so
  nss/libsoftokn3.chk
  nss/libsoftokn3.so
)

libnss3_system_path="$(dirname "$(find_library libnss3.so)")"
libnss3_appdir_path="${appdir}/fallback/libnss3.so" # directory named like library

mkdir -p "${libnss3_appdir_path}/nss"

for file in "${libnss3_files[@]}"; do
  cp -L "${libnss3_system_path}/${file}" "${libnss3_appdir_path}/${file}"
  rm -f "${appdir}/lib/$(basename "${file}")" # in case it was already packaged by linuxdeploy
done

##########################################################################
# TURN APPDIR INTO AN APPIMAGE
##########################################################################

appimage="${appdir%.AppDir}.AppImage" # name to use for AppImage file

appimagetool_args=( # array
  # none
  )

created_files=(
  "${appimage}"
  )

if [[ "${UPDATE_INFORMATION}" ]]; then
  appimagetool_args+=( # append to array
    --updateinformation "${UPDATE_INFORMATION}"
    )
  created_files+=(
    "${appimage}.zsync" # this file will contain delta update data
    )
else
  cat >&2 <<EOF
$0: Automatic updates disabled.
To enable automatic updates, please set the env. variable UPDATE_INFORMATION
according to <https://github.com/AppImage/AppImageSpec/blob/master/draft.md>.
EOF
fi

# create AppImage
appimagetool "${appimagetool_args[@]}" "${appdir}" "${appimage}"

# We are running as root in the Docker image so all created files belong to
# root. Allow non-root users outside the Docker image to access these files.
chmod a+rwx "${created_files[@]}"
parent_dir="${PWD}"
while [[ "$(dirname "${parent_dir}")" != "${parent_dir}" ]]; do
  [[ "$parent_dir" == "/" ]] && break
  chmod a+rwx "$parent_dir"
  parent_dir="$(dirname "$parent_dir")"
done

ls -lh "${created_files[@]}"
echo "Recipe has finished!" >&2
