#!/bin/bash

#
# File .......... swuk_xilinx
# Author ........ Steve Haywood
# Website ....... http://www.spacewire.co.uk
# Project ....... Common (SpaceWire UK Tutorial)
# Date .......... 25 Jun 2025
# Version ....... 2.0
# History .......
#   1.0 common/other/src/script/xilinx.sh
# Description ...
#   Determine and list which Xilinx tools are available and let the user select
# the require ones to use. Does not reverse any previously selected tools upon
# selecting news ones so it is cleaner to use in a new Terminal session.
#
# Examples :-
#
# Bring up menu for user selection of tools
# source swuk_xilinx
#
# Auto-select 2024.2 Vivado, SDK, Vitis & PetaLinux
# source swuk_xilinx 2024.2
#
# Auto-select 2024.4 Vivado, SDK & Vitis & 2021.2 PetaLinux
# source swuk_xilinx 2024.2 2021.2
#

# Strict
#set -euo pipefail


################################################################################
# Source the setup scripts for the selected tools.
# Arguments ...... None
# Return ......... None
# Exit code ...... Status (0=success, 1=failure)
# Shared (In) .... lookup, tools
# Shared (Out) ... None
source_tools() {
  # Declare local variables
  local -i  tindex         # Int: Current tool index (into c_tools array)
  local     loc            # Str: Path to the script directory
  local     bin            # Str: Binary location

  echo -e "\nTools are as follows :-\n"

  # Iterate through list of tools
  for ((tindex=0; tindex<${#c_tools[@]}; tindex++)); do
    # Source tool setup script
    echo -ne "${c_cyan}${c_tools[tindex]}${c_normal} @ "
    if [[ " ${lookup[*]} " =~ " ${tools[tindex]}-${tindex} " ]]; then
      loc="${c_install}/${c_tools[tindex]}/${tools[tindex]}"
      if [ $((tindex+1)) -eq ${#c_tools[@]} ]; then  # PetaLinux
        source "${loc}/${c_settings[tindex]}" "${loc}/tool" > /dev/null 2>&1
      else
        source "${loc}/${c_settings[tindex]}" > /dev/null 2>&1
      fi
      # Display tool location
      bin=$(which "${c_executable[tindex]}")
      [ -z ${bin} ] && echo -ne "${c_red}Not found!" || echo -ne "${c_green}${bin}"
    else
      echo -ne "${c_red}Not available!"
    fi
    echo -e ${c_normal}
  done
}


################################################################################
# Display table of the available tools.
# Arguments ...... None
# Return ......... None
# Exit code ...... Status (0=success, 1=failure)
# Shared (In) .... installs*, lookup*, tools
# Shared (Out) ... installs*, lookup*
display_tools() {
  # Declare local variables
  local -i index    # Int: Current install index (into installs array)
  local    install  # Str: Current install name (from installs array)
  local -i tindex   # Int: Current tool index (into c_tools array)
  local    tool     # Str: Current tool name (from c_tools array)

  # Check for tool installation directory
  if [ -d ${c_install} ]; then

    # Get a list of all subdirectories
    readarray -t installs < <(find ${c_install}/${c_tools[0]} -mindepth 1 -maxdepth 1 \( -type l -o -type d \) -printf '%f\n' | sort )
    [[ ${#installs[@]} -eq 0 ]] && echo "Tools installation directory (${c_install}/${c_tools[0]}) does not contain any ${c_tools[0]} subdirectories!" && return 1

    # Obtain & display table of availability tools
    [[ ${tools[c_vivado]} == "" ]] &&
      echo -e "\nXilinx tools @ ${c_install} :-\n"

    # Iterate through list of subdirectories
    for ((index=0; index<${#installs[@]}; index++)); do
      install=${installs[${index}]}
      # Display table
      [[ ${tools[c_vivado]} == "" ]] &&
        printf "%2.0f) %s" "$((index+1))" "${install}"
      # Iterate through list of tools
      for ((tindex=0; tindex<${#c_tools[@]}; tindex++)); do
        # Add available tool (version + type) to array
        local available=$([ -d "${c_install}/${c_tools[${tindex}]}/${install}" ]; echo $?)
        [ ${available} -eq 0 ] && lookup+=("${install}-${tindex}")
        # Display table
        [[ ${tools[c_vivado]} == "" ]] &&
          echo -ne " - ${c_pass_fail[${available}]}" &&
          echo -ne "${c_tools[${tindex}]}" &&
          echo -ne "${c_normal}"
      done
      [[ ${tools[c_vivado]} == "" ]] &&
        echo
    done

  else
    echo "Tools installation directory (${c_install}) not found!"
    return 1
  fi
}


################################################################################
# User selection of tools.
# Arguments ...... None
# Return ......... None
# Exit code ...... Status (0=success, 1=failure)
# Shared (In) .... installs, tools*
# Shared (Out) ... tools*
user_select() {
  # Declare local variables
  local -i valid          # Int: Input valid (0=no, 1=yes)
  local    input=""       # Str: User input (raw)
  local -a selections=()  # Arr: User input split into array
  local -i index          # Int: Index into selections array

  echo -e "\nq) Quit\n"

  # Clear selected tool versions
  tools=("" "" "" "")

  while true; do

    valid=1

    # Get user selection(s)
    read -e -p "Select tool(s) required [1-${#installs[@]}] or quit : " -i "${#installs[@]} ${#installs[@]}" input

    if [[ ("${input}" == "q") || ("${input}" == "Q") ]]; then
      # Leave
      echo "Quiting without selection!"
      break
    fi

    # Split user selection(s) into array
    read -ra selections <<< "${input}"

    # Initial selection(s) check
    if [[ (${#selections[@]} -eq 1) || (${#selections[@]} -eq 2) ]]; then
      # Iterate through selection(s)
      for ((index=0; index<${#selections[@]}; index++)); do
        if ! [[ "${selections[index]}" =~ ^[0-9]+$ ]] ; then
          echo -e "${c_red}Selection $((index+1)) (${selections[index]}) is not a number!${c_normal}"
          valid=0
          break
        elif [[ (${selections[index]} -lt 1) || (${selections[index]} -gt ${#installs[@]}) ]]; then
          echo -e "${c_red}Selection $((index+1)) (${selections[index]}) is out of range!${c_normal}"
          valid=0
          break
        fi
      done
    else
      echo -e "${c_red}Wrong number of selections, expecting 1 or 2!${c_normal}"
      valid=0
    fi

    # Obtain versions from selections
    if [[ (${valid} -eq 1) && (${index} -eq ${#selections[@]}) ]]; then
      if [[ ${#selections[@]} -eq 1 ]]; then
        tools[c_vivado]=${installs[$((selections[0]-1))]}
        tools[c_sdk]=${tools[c_vivado]}
        tools[c_vitis]=${tools[c_vivado]}
        tools[c_petalinux]=${tools[c_vivado]}
      else
        tools[c_vivado]=${installs[$((selections[0]-1))]}
        tools[c_sdk]=${tools[c_vivado]}
        tools[c_vitis]=${tools[c_vivado]}
        tools[c_petalinux]=${installs[$((selections[1]-1))]}
      fi
      break
    fi

  done
}


################################################################################
# Main function.
# Arguments ...... ${@}
# Return ......... None
# Exit code ...... Status (0=success, 1=failure)
# Shared (In) .... None
# Shared (Out) ... installs*, lookup*, tools*
main()
{
  # Exit if script wasn't sourced
  if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    echo "Script needs to be sourced : source ${BASH_SOURCE[0]}"
    exit 1
  fi

  # Declare tool specific constants
  local -r  c_install="/opt/Xilinx"
  local -ra c_tools=("Vivado" "SDK" "Vitis" "PetaLinux")
  local -ra c_settings=("settings64.sh" "settings64.sh" "settings64.sh" "tool/settings.sh")
  local -ra c_executable=("vivado" "xsdk" "vitis" "petalinux-build")

  # Declare tools array index constants
  local -r  c_vivado=0
  local -r  c_sdk=1
  local -r  c_vitis=2
  local -r  c_petalinux=3

  # Declare terminal colour constants
  local -r  c_red='\033[0;31m'
  local -r  c_green='\033[0;32m'
  local -r  c_blue='\033[0;34m'
  local -r  c_cyan='\033[0;36m'
  local -r  c_normal='\033[0m'
  local -r  c_pass_fail=(${c_green} ${c_red})

  # Declare constants
  local -r  c_opthelp="--help"   # Str: Help option name
  local -Ar c_options=(          # ARR: Options & associated help information
    [${c_opthelp}]="Display this help and exit."
  )
  local -ar c_optorder=(         # Arr: Help options display order
    ${c_opthelp}
  )
  local -ar argv=(${@})          # Arr: Get argument values (space-separated) into array

  # Declare variables
  local     arg                  # Str: Current argument from argv array
  local     option               # Str: Current option from c_optorder array

  # Declare shared variables

  local -a  installs=()          # Arr: Array of install directories (alphanumeric order)
  local -a  lookup=()            # Arr: Array of available tools
  local -a  tools=("" "" "" "")  # Arr: Requested tools (Vivado, SDK, Vitis, PetaLinux)

  # Display help information
  if [[ " ${argv[*]} " =~ " ${c_opthelp} " ]]; then
    echo "Usage: $(basename ${0}) [ALL]... [FIRMWARE/SOFTWARE EMBEDDED]... [OPTION]..."
    echo "Select which version of the Xilinx tools to use; FIRMWARE=Vivado, SOFTWARE=SDK or Vitis & EMBEDDED=PetaLinux."
    echo
    for option in ${c_optorder[@]}
    do
      echo "      ${option} $(printf ' %.0s' {1..12} | head -c $((12-${#option}))) ${c_options[${option}]}"
    done
    echo
    return 0
  fi

  # Get & check the arguments
  for arg in ${argv[@]}; do
    if [[ ${arg:0:2} == "--" ]]; then  # Option
      [[ ! -v c_options[${arg}] ]] && echo "Option (${arg}) is not recognised!" && return 1
    elif [[ "${arg}" =~ ^20[0-9]{2}\.[1-4]{1}$ ]]; then  # Version NNNN.N
      if [ -v ${tools[c_vivado]} ]; then
        tools[c_vivado]=${arg}
      elif [ -v ${tools[c_petalinux]} ]; then
        tools[c_petalinux]=${arg}
      else
        echo "Too many arguments found, expecting 1 or 2!"
        return 1
      fi
    else
      echo "Unexpected argument (${arg}) found!"
      return 1
    fi
  done

  # Ripple single (vivado) argument into sdk, vitis & petalinux
  tools[c_sdk]=${tools[c_vivado]}
  tools[c_vitis]=${tools[c_vivado]}
  [[ (-n ${tools[c_vivado]}) && (! -n ${tools[c_petalinux]}) ]] &&
    tools[c_petalinux]=${tools[c_vivado]}

  display_tools
  [[ ! -n ${tools[c_vivado]} ]] && user_select
  [[ -n ${tools[c_vivado]} ]] && source_tools
}


################################################################################
# Opening gambit.
# Arguments ...... ${@}
# Return ......... None
# Exit code ...... Status (0=success, 1=failure)
# Shared (In) .... None
# Shared (Out) ... None
main ${@}
return 0
