#!/bin/bash
# shellcheck shell=bash

# Copyright Intel Corporation
# SPDX-License-Identifier: MIT
# https://opensource.org/licenses/MIT


#############################################################################
# Script to install all the relevant eclipse plugins into the user's
# installation of Eclipse. This script supports the "Eclipse IDE for C/C++
# Developers" based on versions 4.14 (aka 2019-12) of Eclipse, and later.


# Black        0;30     Dark Gray     1;30
# Red          0;31     Light Red     1;31
# Green        0;32     Light Green   1;32
# Brown/Orange 0;33     Yellow        1;33
# Blue         0;34     Light Blue    1;34
# Purple       0;35     Light Purple  1;35
# Cyan         0;36     Light Cyan    1;36
# Light Gray   0;37     White         1;37

RED='\033[0;31m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

DEBUG=0
SILENT=0
TOPDIR=0
COUNTER=0
ECLIPSE_EXE=""
SCRIPT_NAME=$(basename "$0")
SCRIPT_DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
temp_array=()
installed_features=()
declare -A features_aa
declare -A features_install_aa
declare -A features_update_aa

print_help_message()
{
  printf "\n"
  printf "%s\n" "Usage: path/to/${SCRIPT_NAME}"
  printf "\n"
  printf "%s\n" "This script must be run from: <install-dir>/dev-utilities/<version>/bin"
  printf "\n"
  printf "%s\n" "Run this script to add or update the oneAPI Eclipse plugins in your copy of the"
  printf "%s\n" "\"Eclipse IDE for C/C++ Developers\" package, which can be downloaded from:"
  printf "%s\n" "https://www.eclipse.org/downloads/packages/"
  printf "\n"
  # printf "%s\n" "-s or --silent:  Quiets most output."
  # printf "\n"
  printf "%s\n" "-v or --verbose: Adds additional output. Helpful for debugging."
  printf "\n"
  printf "%s\n" "-h or --help:    Display this help message and exit."
  printf "\n"
  printf "%s\n" "NOTE: This script installs the newest version of each oneAPI Eclipse plugin"
  printf "%s\n" "      that it locates in the oneAPI installation folder."
  printf "\n"

  exit 0
}

# ###########################################################################
# Confirm that we are located at <install-dir>/dev-utilities/<version>/bin
# before attempting to locate or install any oneAPI Eclipse plugins.

if [ "$(basename "$(realpath "${SCRIPT_DIR}/../..")")" != "dev-utilities" ] ; then
  print_help_message ;
fi


# ###########################################################################
# Plugin installation work begins here.

print_info_always()
{
    echo -e "${GREEN}[INFO] $*${NC}"
}

print_info_verbose()
{
  if [ "${SILENT}" == "0" ]; then
    echo -e "${CYAN}[INFO] $*${NC}"
  fi
}

print_info_array()
{
  if [ "${SILENT}" == "0" ]; then
    local -n ARRAY=$1
    for f in "${ARRAY[@]}"
    do
      echo -e "${CYAN}[INFO] ${f}${NC}"
    done
  fi
}

print_error()
{
  echo -e "${RED}[ERROR] $1 ${NC}" >>/dev/stderr
}

print_debug()
{
  if [ "${DEBUG}" == "1" ] && [ "${SILENT}" == "0" ] ; then
    echo -e "${CYAN}[DEBUG] ${NC}${BLUE}$*${NC}"
  fi
}

print_debug_array()
{
  local -n ARRAY=$1
  if [ "${DEBUG}" == "1" ] && [ "${SILENT}" == "0" ] ; then
    for f in "${ARRAY[@]}"
    do
      echo -e "${BLUE}[DEBUG] ${NC}${BLUE}${f}${NC}"
    done
  fi
}

peform_eclipse_uninstall()
{
  # we only need to uninstall the plugins that actually need update
  for f in "${!features_update_aa[@]}"
  do
    echo "Uninstalling ${f}"
    "${1} " \
      "-nosplash " \
      "-application org.eclipse.equinox.p2.director " \
      "-uninstallIU $f.feature.group"
  done
}

perform_eclipse_install()
{
  # 1: eclipe executable
  # 2: feature name without .feature.group
  # 3: Zip file location
  # 4: temp directory where the zip file contents are extracted
  OIFS="$IFS"
  IFS=$'\n'
  # create temp folder to unzip contents of the p2 repo zip into
  while IFS='' read -r line ; do UNZIP_LOG+=("$line") ; done < <(unzip "${3}" -d "${4}/repo_${COUNTER}")
  print_debug_array UNZIP_LOG
  while IFS='' read -r line ; do ECL_LOG+=("$line") ; done < <("${1}" -nosplash -application org.eclipse.equinox.p2.director -repository "file:${4}/repo_${COUNTER}" -installIU "${2}.feature.group")
  print_info_array ECL_LOG
  IFS="$OIFS"
  # Installation has failed or succeeded, remove temp folder created above
  rm -rf "${4}/repo_${COUNTER}"
  # increment counter to avoid conflicts, there shouldn't be any in the first
  # place though
  COUNTER=$((COUNTER + 1))
}

eclipse_install_helper()
{
  # run through the update zip file
  print_info_always "Updating features (if any updates needed)..."
  for f in "${!features_update_aa[@]}"
  do
    feature_zip="$(cut -d',' -f1 <<<"${features_update_aa[${f}]}")"
    # unzip the zip file to create repo in folder, this will be deleted after
    perform_eclipse_install "${1}" "${f}" "${feature_zip}" "${2}"
  done
  # run through features to be installed
  print_info_always "Installing features previously not present (if new features found)..."
  for f in "${!features_install_aa[@]}"
  do
    feature_zip="$(cut -d',' -f1 <<<"${features_install_aa[${f}]}")"
    # unzip the zip file to create repo in folder, this will be deleted after
    perform_eclipse_install "${1}" "${f}" "${feature_zip}" "${2}"
  done
}

perform_eclipse_garbage_collection()
{
  print_info_verbose "Running Garbage Collector on Eclipse: ${1}"
  OIFS="$IFS"
  IFS=$'\n'
  # ECL_LOG=($("${1}" -nosplash -application org.eclipse.equinox.p2.garbagecollector.application))
  while IFS='' read -r line ; do ECL_LOG+=("$line") ; done < <("${1}" -nosplash -application org.eclipse.equinox.p2.garbagecollector.application)
  print_info_array ECL_LOG
  IFS="$OIFS"
}

# Function to sift through the plugins found and which of those can be installed
prepare_install_shortlist()
{
  # go through the list of features found in the installation
  for local_f in "${!features_aa[@]}"
  do
    feature_name="$(cut -d'/' -f2 <<<"$local_f")"
    print_debug "${feature_name}"
    # this flag will keep a track of if the feature has been found
    # update required or not, if a feature is present then no need to fresh install it
    FLAG=0
    for inst_f in "${installed_features[@]}"
    do
      if [[ "${inst_f}" == *"${feature_name}"* ]]; then
        FLAG=1
        print_info_verbose "Found feature installed ${inst_f}"
        # need to check for versions and see if update is needed
        # installed feature version
        installed_f_v="$(cut -d'/' -f2 <<<"$inst_f")"
        # incoming feature version
        incoming_f_v="$(cut -d',' -f2 <<<"${features_aa[${local_f}]}")"
        print_info_verbose "Installed Version: ${installed_f_v}"
        print_info_verbose "Incoming Version: ${incoming_f_v}"
        function_that_returns_the_newer_version "${installed_f_v}" "${incoming_f_v}"
        return_val=$?
        print_debug "Larger Value: ${return_val}"
        # if the incoming version is greater then an update is required
        if [ ${return_val} -eq 2 ]; then
          features_update_aa[${feature_name}]=${features_aa[${local_f}]}
        fi
      fi
    done
    # if the feature was not found installed in the installed roots, then a
    # fresh install is needed
    if [ ${FLAG} == 0 ]; then
      features_install_aa[${feature_name}]=${features_aa[${local_f}]}
    fi
  done
  print_info_verbose "Number of Features found for Installation: ${#features_install_aa[@]}"
  print_info_verbose "Number of Features found for Update: ${#features_update_aa[@]}"
}

# https://stackoverflow.com/questions/4023830/how-to-compare-two-strings-in-dot-separated-version-format-in-bash
function_that_returns_the_newer_version()
{
  if [ "${1}" == "${2}" ]; then
    return 0;
  fi
  local IFS=$'.'
  # Here we are splitting a version iinto an alphanumeric array with . being the
  # delimiter
  local i ver1=("$1") ver2=("$2")
  # sort out versions here by making them have equal number of components
  # using Z because it'll rank higher in a lexicographical comparison
  # the below logic evens out two version strings which might now have the same
  # version components. SO if V1 is 4 components and V2 is 2 then, V2 will be
  # evened out by suffixing .Z.Z onto it.
  # example: V1 - 1.0.0.0 , V2 - 0.1
  # post logic: V2 - 0.1.Z.Z
  if [ ${#ver1[@]} -gt ${#ver2[@]} ]; then
    for ((i=${#ver2[@]}; i<${#ver1[@]}; i++))
    do
      ver2[i]=Z
    done
  elif [ ${#ver2[@]} -gt ${#ver1[@]} ]; then
    for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))
    do
      ver1[i]=Z
    done
  fi

  print_debug "Version1: " "${ver1[@]}"
  print_debug "Version2: " "${ver2[@]}"

  # start comparing versions
  # the versions will be compared version component to version component. For
  # example, major with major, minor with  minor, update with update and etc
  # with etc
  for ((i=0; i<${#ver1[@]}; i++))
  do
    # checking if corresponding versions are integers or alphanumeric
    print_debug "Comparing ${ver1[i]} and ${ver2[i]}"
    if [[ ! ${ver1[i]} =~ ^-?[0-9]+$ ]] || [[ ! ${ver1[i]} =~ ^-?[0-9]+$ ]]; then
      print_debug "Either version 1 or version 2 is not a pure integer"
      # in this case we use \> and \< for comparison which eventually results in
      # a lexicographical(wow!) comparison using sort. Also must use quotes.
      if [ "${ver1[i]}" \> "${ver2[i]}" ]; then
        return 1
      elif [ "${ver2[i]}" \> "${ver1[i]}" ]; then
        return 2
      fi
    else
      # this is a direct interger to integer comparison
      print_debug "Both numbers are pure integers"
      if [ ${ver1[i]} -gt ${ver2[i]} ]; then
        return 1
      elif [ ${ver2[i]} -gt ${ver1[i]} ]; then
        return 2
      fi
    fi
  done
}

check_if_executable()
{
  for i in 1 2 3
  do
    echo -e "${GREEN}Please enter the path to the Eclipse executable: ${NC}"
    # shellcheck disable=SC2162
    read -ep "" USER_INPUT
    eval USER_INPUT="$USER_INPUT"
    print_info_always "Looking for an Eclipse executable here: ${USER_INPUT}"
    if [[ -f "${USER_INPUT}" ]]; then
      print_debug "${USER_INPUT} is a file"
      if [[ -x "${USER_INPUT}" ]]; then
        print_debug "${USER_INPUT} is an executable"
        ECLIPSE_EXE=${USER_INPUT}
        break
      else
        print_error "${USER_INPUT} is a file but not an executable file."
        continue
      fi
    elif [[ -d "${USER_INPUT}" ]]; then
      print_debug "${USER_INPUT} is a directory"
      if [[ ! -x "${USER_INPUT}/eclipse" ]] || [[ ! -f "${USER_INPUT}/eclipse" ]]; then
        print_error "Could not find the eclipse executable under the directory ${USER_INPUT}"
        continue
      else
        print_debug "${USER_INPUT}/eclipse is an executable and exists"
        ECLIPSE_EXE=${USER_INPUT%/}/eclipse
        break
      fi
    fi
  done
  if [ "${ECLIPSE_EXE}" == "" ]; then
    echo "Eclipse executable: ${ECLIPSE_EXE}"
    print_error "Unable to find Eclipse executable."
    exit 0
  fi
}

perform_script_function()
{
  # setting up TOPDIR
  TOPDIR="${SCRIPT_DIR}/../../../"
  # get eclipse path
  if [ "${SILENT}" == "0" ]; then
    check_if_executable "${ECLIPSE_EXE}"
  else
    if [[ -f "${ECLIPSE_EXE}" ]]; then
      print_debug "${ECLIPSE_EXE} is a file"
      if [[ ! -x "${ECLIPSE_EXE}" ]]; then
        print_error "Not an Eclipse executable"
        exit 0
      fi
    elif [[ -d "${ECLIPSE_EXE}" ]]; then
      print_debug "${ECLIPSE_EXE} is a directory"
      if [[ ! -x "${ECLIPSE_EXE}/eclipse" ]]; then
        print_error "Could not find the eclipse executable under the directory ${ECLIPSE_EXE}"
        exit 0
      fi
    fi
  fi
  # Gather features to be installed or updated
  IDE_SUPPORT_FOLDERS=()
  eclipse_files_array=()
  # find all the ide_support or ide-support folders
  print_debug "Looking for IDE Support folders"
  OIFS="$IFS"
  IFS=$'\n'
  while IFS='' read -r line ; do IDE_SUPPORT_FOLDERS+=("$line") ; done < <(find "${TOPDIR}" -iname 'ide*support')
  print_debug_array IDE_SUPPORT_FOLDERS
  print_info_always "Searching for Eclipse Plugins zip files"
  for path in "${IDE_SUPPORT_FOLDERS[@]}"
  do
    while IFS='' read -r line ; do temp_array+=("$line") ; done < <(find "${path}/" -iname '*.zip')
    for file in "${temp_array[@]}"
    do
      eclipse_files_array+=("${file}")
    done
  done
  print_debug_array eclipse_files_array
  IFS="$OIFS"
  if [ ${#eclipse_files_array[@]} -eq 0 ]; then
    print_error "No zip files present in the installation"
    exit 0
  fi
  # using associative arrays for now, duplicates are not allowed, so need to
  # figure out a way around that
  for file in "${eclipse_files_array[@]}"
  do
    # var carries the unzip list output
    var=$(unzip -l "${file}")
    print_info_verbose "Searching for features under: ${file}"
    # get the feature name, version etc and avoid source features
    feature_composite=`echo "${var}" | grep -io "features\/[A-Za-z0-9_.]*jar" | grep -iv "source"`
    feature_composite_array=(${feature_composite})
    for temp_var in "${feature_composite_array[@]}"
    do
      OIFS="$IFS"
      IFS=$'_'
      # split the feature name from the version
      # temp_var: features/com.intel.some.feature_1.0.0.jar
      # feature_name: features/com.intel.some.feature
      # version_cut: 1.0.0.123456.jar
      read -r feature_name version_cut <<< "$temp_var"
      IFS="$OIFS"
      # get rid if the trailing .jar
      version_cut=${version_cut%".jar"}
      print_debug "Working with Feature Name: ${feature_name}"
      print_debug "Working with Version: ${version_cut}"
      # comparison logic (beware of illogical logic!)
      # check if the feature already exists
      if [ -v features_aa["${feature_name}"] ]; then
        print_debug "${feature_name} already present in array, will compare versions"
        # $1 will always be new string and $2 will always be the one that already
        # exists in the associative array
        two=${features_aa[${feature_name}]}
        # fetch the version value from the associative array
        two="$(cut -d',' -f2 <<<"$two")"
        function_that_returns_the_newer_version "$version_cut" "$two"
        ret_val=$?
        if [ ${ret_val} -eq 1 ]; then
          print_debug "Incoming feature found: ${feature_name}"
          print_debug "Found feature version: ${version_cut}"
          print_debug "Found feature plugin file: ${file}"
          features_aa[${feature_name}]=$file,$version_cut
        fi
        print_debug "Version comparison return: $?"
      else
        features_aa[${feature_name}]=$file,$version_cut
      fi
    done
  done
  if [ ${#features_aa[@]} -eq 0 ]; then
    print_error "No features found in the zip files present installation"
    exit 0
  fi
  print_info_always "Found Features:"
  for f in "${!features_aa[@]}"
  do
    print_info_always "$f"
  done

  # list installed units
  print_debug "Listing installed IUs in Eclipse: ${ECLIPSE_EXE}"
  while IFS='' read -r line ; do installed_features+=("$line") ; done < <("${ECLIPSE_EXE}" -nosplash -application org.eclipse.equinox.p2.director -listInstalledRoots)
  print_debug_array installed_features

  # prepare shortlist of plugins
  prepare_install_shortlist installed_features features_aa

  # Create temp dir under the users home dir
  TDIR=$(mktemp -d "${HOME}"/plugins.XXXXXX)
  print_info_verbose "Temporary Directory Created: ${TDIR}"

  print_info_verbose "............................................."
  print_debug "Beginning uninstallation of packages"
  peform_eclipse_uninstall "${ECLIPSE_EXE}"
  print_debug "Beginning Garbage Collection of leftover plugins"
  perform_eclipse_garbage_collection "${ECLIPSE_EXE}"
  print_info_verbose "............................................."
  print_debug "Beginning installation of plugins"
  eclipse_install_helper "${ECLIPSE_EXE}" "${TDIR}"
  print_debug "Removing ${TDIR}"
  rm -rf "${TDIR}"
  print_info_verbose "............................................."
  exit 0
}

main()
{
  # read user input
  if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
    print_help_message
  elif [ "$1" == "-v" ] || [ "$1" == "--verbose" ]; then
    DEBUG=1
    perform_script_function
  # elif [ "$1" == "-s" ] || [ "$1" == "--silent" ]; then
    # SILENT=1
    # ECLIPSE_EXE=$2
    # perform_script_function
  elif [ "$1" == "" ]; then
    # proceed as normal
    perform_script_function
  else
    print_help_message
  fi
}

main "$1" "$2"
