#!/usr/bin/env bash
# This script will install Posit's VS Code extension.

set -e

s3_bucket=https://rsw-vscode-extension.s3.amazonaws.com
server_bin_dir=/usr/lib/rstudio-server/bin
ext_file="$server_bin_dir/vscode-workbench-ext/rstudio-workbench.vsix"
sys_version_file="$server_bin_dir/vscode-workbench-ext/.version"
user_vscode_extension_dir="$HOME/.local/share/rstudio/vscode-extensions"
user_version_file="${user_vscode_extension_dir}/.version"

function help() {
	cat <<EOF
usage: install-vs-code-ext.sh [options]

Install Posit's VS Code extension, which enables integration between VS Code and Posit Workbench.

In normal usage, the extension that was installed with Posit Workbench is used.

The -v option allows downloading and installing a specific version, but this should only be done if 
instructed by a Posit support team member. It will replace the version that was installed with
Posit Workbench.

If a global extensions directory has been configured in vscode.conf, the extension will be copied to
it and installed. You may also specify `-l` to install locally (only for the current user).

Examples
   install-vs-code-ext.sh

Options
   -f
         Install extensions from vscode.extensions.conf.

   -l
         Install the Posit extension for the current user.

   -v <version>
         Download a specific version of the Posit extension, overwriting the version that was
         installed with the product.
   
   -d
         Allow VS Code to downgrade to an older extension version that the one currently installed.
         Use in conjunction with -l and/or -f.

   -h
         Show this help.
EOF
}

# given a file "foo" find a unique name foo.bak, appending a number
# if necessary to find a unique name (foo.bak2, etc.)
function nextsuffix {
   local name="$1.bak"
   if [ ! -e "$name" ]; then
      printf "%s" "$name"
   else
      local -i num=2
      while [ -e "$name$num" ]; do
         num+=1
      done
      printf "%s%d" "$name" "$num"
   fi
}

# Given the path to the code-server binary, return the product version.
# Do this *without* invoking `code-server -v` by instead examining
# product.json. This avoids the side effect of code-server creating
# ~/.config/code-server, potentially owned by root, which will break
# the ability to install extensions by the non-root user.
function getCodeServerVersion {
	local binaryLocation="$(dirname ${1})"
	local packageFile="$(dirname ${binaryLocation})/package.json"
	if [ ! -f "${packageFile}" ]; then
		# Older code-server builds had code-server binary in the top-level
		# folder (alongside package.json) instead of in a bin folder.
		packageFile="${binaryLocation}/package.json"
		if [ ! -f "${packageFile}" ]; then
			printf "0.0.0"
			return 0
		fi
	fi
	packageVersion=$(cat ${packageFile} | grep version | head -1 | awk -F= "{ print $2 }" | sed 's/[version:,\",]//g' | tr -d '[[:space:]]')
	printf $packageVersion
}

while getopts dfhlv: flag
do
    case "${flag}" in
        d) allow_downgrade=1;;
        f) install_ext_file=1;;
        l) local=1;;
        v) ext_version=${OPTARG};
            if [[ ! "$ext_version" =~ ^([0-9]+.){2}[0-9]+$ ]]
            then
                echo 'error: invalid version '$ext_version' - expecting three numbers separated by periods\n';
                exit 1
            fi
            ;;
        h)
           help
           exit 0
           ;;
    esac
done

# load and parse vscode config file
configDirs="$XDG_CONFIG_DIRS:/etc"
origIFS=$IFS
IFS=':'; read -ra configDirList <<< "$configDirs"
for configDir in "${configDirList[@]}"
do
   if test -e "$configDir/rstudio/vscode.conf"
   then
      configFile="$configDir/rstudio/vscode.conf"
      break
   fi
done
IFS=$origIFS

# exit if we cannot determine the vs code executable
if [ ! -f "$configFile" ]
then
   if [[ -z "$VSCODE_EXEC_COMMAND" ]]
   then
      if [ -z "$POST_INSTALL_SCRIPT" ]; then
         echo "ERROR: Could not install extension. The code-server executable must be set by VSCODE_EXEC_COMMAND or as exe in vscode.conf."
      fi
      exit 1
   else
      cs_exe=$VSCODE_EXEC_COMMAND
   fi
else
   cs_exe=$(grep exe $configFile | sed 's/.*\(exe=\)\(.[^ ]*\).*/\2/')
   if [ -z "$cs_exe" ]; then
      # use the default; starting with Elsbeth Geranium we bundle code-server
      cs_exe="$server_bin_dir/code-server/bin/code-server"
   fi
   ext_dir=$(grep 'extensions-dir[= ]' $configFile | sed 's/.*\(extensions-dir[= ]\)\(.[^ ]*\).*/\2/')
   if [ ! -z "$ext_dir" ] && [ ! -d "$ext_dir" ]
   then
      echo "$ext_dir does not exist - creating..."
      mkdir $ext_dir
   fi
fi

# download requested version and replace the one bundled with Workbench; the current 
# extension will be renamed to *.bak (with a number if needed to make unique, *.bak2, etc)
# intended for troubleshooting and testing, not for normal use
if [ ! -z $ext_version ]; then
   echo -e "\nDownloading version $ext_version of the Posit Workbench VS Code extension ...\n"
   download_name="$server_bin_dir/vscode-workbench-ext/rstudio-workbench-$ext_version.vsix"
   curl -L "${s3_bucket}/rstudio-workbench-$ext_version.vsix" -o "$download_name"
   if [ -e "$ext_file" ]
   then
      backup_file="$(nextsuffix "$ext_file")"
      echo -e "\nRenaming current extension to $backup_file and replacing with downloaded version"
      mv "$ext_file" "$backup_file"
      echo "${ext_version}" | tee "${sys_version_file}"
   fi
   mv "$download_name" "$ext_file"
fi

echo "Starting VS Code management process..."

# Compare installed version of code-server with the recommended version
latestCodeServerVersion=4.9.1
latestCodeServerMajor=4
latestCodeServerMinor=9
latestCodeServerPatch=1

# compare installed version of code-server with the minimum required
minCodeServerVersion=3.9.3
minCodeServerMajor=3
minCodeServerMinor=9
minCodeServerPatch=3

# retrieve installed code-server version
codeServerVersion=$(getCodeServerVersion "$cs_exe")
codeServerMajor=$(echo $codeServerVersion | sed 's/\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)/\1/')
codeServerMinor=$(echo $codeServerVersion | sed 's/\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)/\2/')
codeServerPatch=$(echo $codeServerVersion | sed 's/\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)/\3/')

# check if upgrade required
if [[ -z "$codeServerVersion" || -z "$codeServerMajor" || -z "$codeServerMinor" || -z "$codeServerPatch" ]]
then
  echo "Warning: Could not parse code-server version. Required version is $minCodeServerVersion."
else
   if [[ $codeServerMajor -lt $minCodeServerMajor ]]; then
      needToUpgrade=1
   elif [[ $codeServerMajor -eq $minCodeServerMajor &&
           $codeServerMinor -lt $minCodeServerMinor ]]; then
      needToUpgrade=1
   elif [[ $codeServerMajor -eq $minCodeServerMajor &&
           $codeServerMinor -eq $minCodeServerMinor &&
           $codeServerPatch -lt $minCodeServerPatch ]]; then
      needToUpgrade=1
   fi

   if [[ $codeServerMajor -lt $latestCodeServerMajor ]]; then
      wantToUpgrade=1
   elif [[ $codeServerMajor -eq $latestCodeServerMajor &&
         $codeServerMinor -lt $latestCodeServerMinor ]]; then
      wantToUpgrade=1
   elif [[ $codeServerMajor -eq $latestCodeServerMajor &&
         $codeServerMinor -eq $latestCodeServerMinor &&
         $codeServerPatch -lt $latestCodeServerPatch ]]; then
      wantToUpgrade=1
   fi
fi

if [[ $needToUpgrade -ne 1 && $wantToUpgrade -eq 1 ]]
then
   # warn that a newer version of code-server is recommended and then continue on
   1>&2 echo -e "\nWARNING Posit Workbench VSCode Extension requires code-server to have newer" \
         "version than '$minCodeServerVersion' in order to esatablish secure SSL communication with the code-server session. " \
         "The recommended version of code-server is '$latestCodeServerVersion' and the current version is '$codeServerVersion'. " \
         "Run 'configure-vs-code' to reconfigure Workbench to use the version of code-server installed with Workbench.\n"
fi

if [[ $needToUpgrade -eq 1 ]]
then
   # warn and exit if upgrade code-server is required
   1>&2 echo -e "\nWARNING Posit Workbench VSCode Extension requires code-server to have a" \
         "minimum version of '$minCodeServerVersion', and the current version is '$codeServerVersion'. " \
         "The recommended version of code-server is '$latestCodeServerVersion'. " \
         "Run 'configure-vs-code' to reconfigure Workbench to use the version of code-server installed with Workbench.\n"
   exit 1
fi

# read the extension config file to see if we should install any other extensions
if test -e "$configDir/rstudio/vscode.extensions.conf"
then
   extFile="$configDir/rstudio/vscode.extensions.conf"
fi

if [[ $allow_downgrade -eq 1 ]]; then
   echo "Allowing extension downgrades via --force option"
   extra_cs_args="--force"
fi

# install the extension bundled with Workbench
if [[ $local -eq 1 ]]
then
   # check if bundled extension needs to be installed
   extension_already_installed=0
   version_to_install=''
   if [ -f "$sys_version_file" ]
   then
      version_to_install=$(<${sys_version_file})
   else
      echo "Warning: The Posit Workbench VS Code extension version file is missing"
   fi

   if [ -f "$user_version_file" ]
   then
      installed_version=$(<${user_version_file})
   
      if [ "$installed_version" == "$version_to_install" ]
      then
         extension_already_installed=1
      fi
   fi

   # install Workbench extension 
   if [[ $extension_already_installed -eq 0 ]]
   then
      echo "Installing Posit Workbench VS Code extension for $USER ..."
      $cs_exe "$extra_cs_args" --install-extension "$ext_file"

      # update the user's version file
      mkdir -p ${user_vscode_extension_dir}
      echo "${version_to_install}" | tee "${user_version_file}"
   else
      echo "Skipping extension install ..."
   fi

   # install vscode.extensions.conf extensions
   if [[ $install_ext_file -eq 1 ]]; then
      # we don't throw an error here if the file doesn't exist because it's not required
      if [ ! -z "$extFile" ]; then
         echo "Installing extensions from "$extFile"..."
         cat "$extFile" | while read line
         do
            ext_arg=$(echo "$line" | sed 's/ *$//g')
            if [[ ! -z "$ext_arg" && ${ext_arg:0:1} != "#" ]]; then
               echo -e '\nRunning command "'$cs_exe' --install-extension '$ext_arg'"...\n'
               echo "$ext_arg" | xargs "$cs_exe" "$extra_cs_args" --install-extension
            fi
         done
      fi
   fi
   exit 0
fi

# copy the extension to the global extensions directory
if [ $ext_dir ]
then
   # install extension
   echo "Installing the Posit Workbench VS Code extension ..."
   "$cs_exe" "$extra_cs_args" --extensions-dir "$ext_dir" --install-extension "$ext_file"

   if [ ! -z "$extFile" ]; then
      echo "Installing extensions from "$extFile"..."
      cat "$extFile" | while read line
      do
         ext_arg=$(echo "$line" | sed 's/ *$//g')
         if [[ ! -z "$ext_arg" && ${ext_arg:0:1} != "#" ]]; then
            echo -e '\nRunning command "'$cs_exe' --extensions-dir '$ext_dir' --install-extension '$ext_arg'"...\n'
            echo "$ext_arg" | xargs "$cs_exe" "$extra_cs_args" --extensions-dir "$ext_dir" --install-extension
         fi
      done
   fi
   exit 0
fi
