#!/usr/bin/env bash

# ============================================================ #
# ================== < FLUXION Parameters > ================== #
# ============================================================ #
# Path to directory containing the FLUXION executable script.
readonly FLUXIONPath=$(dirname $(readlink -f "$0"))

# Path to directory containing the FLUXION library (scripts).
readonly FLUXIONLibPath="$FLUXIONPath/lib"

# Path to the temp. directory available to FLUXION & subscripts.
readonly FLUXIONWorkspacePath="/tmp/fluxspace"
readonly FLUXIONIPTablesBackup="$FLUXIONPath/iptables-rules"

# Path to FLUXION's preferences file, to be loaded afterward.
readonly FLUXIONPreferencesFile="$FLUXIONPath/preferences/preferences.conf"

# Constants denoting the reference noise floor & ceiling levels.
# These are used by the the wireless network scanner visualizer.
readonly FLUXIONNoiseFloor=-90
readonly FLUXIONNoiseCeiling=-60

readonly FLUXIONVersion=6
readonly FLUXIONRevision=5

# Declare window ration bigger = smaller windows
FLUXIONWindowRatio=4

# Allow to skip dependencies if required, not recommended
FLUXIONSkipDependencies=1

# Check if there are any missing dependencies
FLUXIONMissingDependencies=0

# Allow to use 5ghz support
FLUXIONEnable5GHZ=0

# ============================================================ #
# ================= < Script Sanity Checks > ================= #
# ============================================================ #
if [ $EUID -ne 0 ]; then # Super User Check
  echo -e "\\033[31mAborted, please execute the script as root.\\033[0m"; exit 1
fi

# ===================== < XTerm Checks > ===================== #
# TODO: Run the checks below only if we're not using tmux.
if [ ! "${DISPLAY:-}" ]; then # Assure display is available.
  echo -e "\\033[31mAborted, X (graphical) session unavailable.\\033[0m"; exit 2
fi

if ! hash xdpyinfo 2>/dev/null; then # Assure display probe.
  echo -e "\\033[31mAborted, xdpyinfo is unavailable.\\033[0m"; exit 3
fi

if ! xdpyinfo &>/dev/null; then # Assure display info available.
  echo -e "\\033[31mAborted, xterm test session failed.\\033[0m"; exit 4
fi

# ================ < Parameter Parser Check > ================ #
getopt --test > /dev/null # Assure enhanced getopt (returns 4).
if [ $? -ne 4 ]; then
  echo "\\033[31mAborted, enhanced getopt isn't available.\\033[0m"; exit 5
fi

# =============== < Working Directory Check > ================ #
if ! mkdir -p "$FLUXIONWorkspacePath" &> /dev/null; then
  echo "\\033[31mAborted, can't generate a workspace directory.\\033[0m"; exit 6
fi

# Once sanity check is passed, we can start to load everything.

# ============================================================ #
# =================== < Library Includes > =================== #
# ============================================================ #
source "$FLUXIONLibPath/installer/InstallerUtils.sh"
source "$FLUXIONLibPath/InterfaceUtils.sh"
source "$FLUXIONLibPath/SandboxUtils.sh"
source "$FLUXIONLibPath/FormatUtils.sh"
source "$FLUXIONLibPath/ColorUtils.sh"
source "$FLUXIONLibPath/IOUtils.sh"
source "$FLUXIONLibPath/HashUtils.sh"
source "$FLUXIONLibPath/HelpUtils.sh"

# NOTE: These are configured after arguments are loaded (later).

# ============================================================ #
# =================== < Parse Parameters > =================== #
# ============================================================ #
if ! FLUXIONCLIArguments=$(
    getopt --options="vdk5rinmtbhe:c:l:a:r" \
      --longoptions="debug,version,killer,5ghz,installer,reloader,help,airmon-ng,multiplexer,target,test,auto,bssid:,essid:,channel:,language:,attack:,ratio,skip-dependencies" \
      --name="FLUXION V$FLUXIONVersion.$FLUXIONRevision" -- "$@"
  ); then
  echo -e "${CRed}Aborted$CClr, parameter error detected..."; exit 5
fi

AttackCLIArguments=${FLUXIONCLIArguments##* -- }
readonly FLUXIONCLIArguments=${FLUXIONCLIArguments%%-- *}
if [ "$AttackCLIArguments" = "$FLUXIONCLIArguments" ]; then
  AttackCLIArguments=""
fi


# ============================================================ #
# ================== < Load Configurables > ================== #
# ============================================================ #

# ============= < Argument Loaded Configurables > ============ #
eval set -- "$FLUXIONCLIArguments" # Set environment parameters.

#[ "$1" != "--" ] && readonly FLUXIONAuto=1 # Auto-mode if using CLI.
while [ "$1" != "" ] && [ "$1" != "--" ]; do
  case "$1" in
    -v|--version) echo "FLUXION V$FLUXIONVersion.$FLUXIONRevision"; exit;;
    -h|--help) fluxion_help; exit;;
    -d|--debug) readonly FLUXIONDebug=1;;
    -k|--killer) readonly FLUXIONWIKillProcesses=1;;
    -5|--5ghz) FLUXIONEnable5GHZ=1;;
    -r|--reloader) readonly FLUXIONWIReloadDriver=1;;
    -n|--airmon-ng) readonly FLUXIONAirmonNG=1;;
    -m|--multiplexer) readonly FLUXIONTMux=1;;
    -b|--bssid) FluxionTargetMAC=$2; shift;;
    -e|--essid) FluxionTargetSSID=$2; shift;
      # TODO: Rearrange declarations to have routines available for use here.
      FluxionTargetSSIDClean=$(echo "$FluxionTargetSSID" | sed -r 's/( |\/|\.|\~|\\)+/_/g'); shift;;
    -c|--channel) FluxionTargetChannel=$2; shift;;
    -l|--language) FluxionLanguage=$2; shift;;
    -a|--attack) FluxionAttack=$2; shift;;
    -i|--install) FLUXIONSkipDependencies=0; shift;;
    --ratio) FLUXIONWindowRatio=$2; shift;;
    --auto) readonly FLUXIONAuto=1;;
    --skip-dependencies) readonly FLUXIONSkipDependencies=1;;
  esac
  shift # Shift new parameters
done

shift # Remove "--" to prepare for attacks to read parameters.
# Executable arguments are handled after subroutine definition.

# =================== < User Preferences > =================== #
# Load user-defined preferences if there's an executable script.
# If no script exists, prepare one for the user to store config.
# WARNING: Preferences file must assure no redeclared constants.
if [ -x "$FLUXIONPreferencesFile" ]; then
  source "$FLUXIONPreferencesFile"
else
  echo '#!/usr/bin/env bash' > "$FLUXIONPreferencesFile"
  chmod u+x "$FLUXIONPreferencesFile"
fi

# ================ < Configurable Constants > ================ #
if [ "$FLUXIONAuto" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONAuto=${FLUXIONAuto:+1}
fi

if [ "$FLUXIONDebug" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONDebug=${FLUXIONDebug:+1}
fi

if [ "$FLUXIONAirmonNG" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONAirmonNG=${FLUXIONAirmonNG:+1}
fi

if [ "$FLUXIONWIKillProcesses" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONWIKillProcesses=${FLUXIONWIKillProcesses:+1}
fi

if [ "$FLUXIONWIReloadDriver" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONWIReloadDriver=${FLUXIONWIReloadDriver:+1}
fi

# FLUXIONDebug [Normal Mode "" / Developer Mode 1]
if [ $FLUXIONDebug ]; then
  :> /tmp/fluxion_debug_log
  readonly FLUXIONOutputDevice="/tmp/fluxion_debug_log"
  readonly FLUXIONHoldXterm="-hold"
else
  readonly FLUXIONOutputDevice="/dev/null"
  readonly FLUXIONHoldXterm=""
fi

# ================ < Configurable Variables > ================ #
readonly FLUXIONPromptDefault="$CRed[${CSBlu}fluxion$CSYel@$CSWht$HOSTNAME$CClr$CRed]-[$CSYel~$CClr$CRed]$CClr "
FLUXIONPrompt=$FLUXIONPromptDefault

readonly FLUXIONVLineDefault="$CRed[$CSYel*$CClr$CRed]$CClr"
FLUXIONVLine=$FLUXIONVLineDefault

# ================== < Library Parameters > ================== #
readonly InterfaceUtilsOutputDevice="$FLUXIONOutputDevice"

readonly SandboxWorkspacePath="$FLUXIONWorkspacePath"
readonly SandboxOutputDevice="$FLUXIONOutputDevice"

readonly InstallerUtilsWorkspacePath="$FLUXIONWorkspacePath"
readonly InstallerUtilsOutputDevice="$FLUXIONOutputDevice"
readonly InstallerUtilsNoticeMark="$FLUXIONVLine"

readonly PackageManagerLog="$InstallerUtilsWorkspacePath/package_manager.log"

declare  IOUtilsHeader="fluxion_header"
readonly IOUtilsQueryMark="$FLUXIONVLine"
readonly IOUtilsPrompt="$FLUXIONPrompt"

readonly HashOutputDevice="$FLUXIONOutputDevice"

# ============================================================ #
# =================== < Default Language > =================== #
# ============================================================ #
# Set by default in case fluxion is aborted before setting one.
source "$FLUXIONPath/language/en.sh"

# ============================================================ #
# ================== < Startup & Shutdown > ================== #
# ============================================================ #
fluxion_startup() {
  if [ "$FLUXIONDebug" ]; then return 1; fi

  # Make sure that we save the iptable files
  iptables-save >"$FLUXIONIPTablesBackup"
  local banner=()

  format_center_literals \
    " ⌠▓▒▓▒   ⌠▓╗     ⌠█┐ ┌█   ┌▓\  /▓┐   ⌠▓╖   ⌠◙▒▓▒◙   ⌠█\  ☒┐"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    " ║▒_     │▒║     │▒║ ║▒    \▒\/▒/    │☢╫   │▒┌╤┐▒   ║▓▒\ ▓║"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    " ≡◙◙     ║◙║     ║◙║ ║◙      ◙◙      ║¤▒   ║▓║☯║▓   ♜◙\✪\◙♜"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    " ║▒      │▒║__   │▒└_┘▒    /▒/\▒\    │☢╫   │▒└╧┘▒   ║█ \▒█║"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    " ⌡▓      ⌡◘▒▓▒   ⌡◘▒▓▒◘   └▓/  \▓┘   ⌡▓╝   ⌡◙▒▓▒◙   ⌡▓  \▓┘"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    "¯¯¯     ¯¯¯¯¯¯  ¯¯¯¯¯¯¯  ¯¯¯    ¯¯¯ ¯¯¯¯  ¯¯¯¯¯¯¯  ¯¯¯¯¯¯¯¯"
  banner+=("$FormatCenterLiterals")

  clear

  if [ "$FLUXIONAuto" ]; then echo -e "$CBlu"; else echo -e "$CRed"; fi

  for line in "${banner[@]}"; do
    echo "$line"; sleep 0.05
  done

  echo # Do not remove.

  sleep 0.1
  local -r fluxionRepository="https://github.com/FluxionNetwork/fluxion"
  format_center_literals "${CGrn}Site: ${CRed}$fluxionRepository$CClr"
  echo -e "$FormatCenterLiterals"

  sleep 0.1
  local -r versionInfo="${CSRed}FLUXION $FLUXIONVersion$CClr"
  local -r revisionInfo="(rev. $CSBlu$FLUXIONRevision$CClr)"
  local -r credits="by$CCyn FluxionNetwork$CClr"
  format_center_literals "$versionInfo $revisionInfo $credits"
  echo -e "$FormatCenterLiterals"

  sleep 0.1
  local -r fluxionDomain="raw.githubusercontent.com"
  local -r fluxionPath="FluxionNetwork/fluxion/master/fluxion.sh"
  local -r updateDomain="github.com"
  local -r updatePath="FluxionNetwork/fluxion/archive/master.zip"
  if installer_utils_check_update "https://$fluxionDomain/$fluxionPath" \
    "FLUXIONVersion=" "FLUXIONRevision=" \
    $FLUXIONVersion $FLUXIONRevision; then
    installer_utils_run_update "https://$updateDomain/$updatePath" \
      "FLUXION-V$FLUXIONVersion.$FLUXIONRevision" "$FLUXIONPath"
    fluxion_shutdown
  fi

  echo # Do not remove.

  local requiredCLITools=(
    "aircrack-ng" "bc" "awk:awk|gawk|mawk"
    "curl" "cowpatty" "dhcpd:isc-dhcp-server|dhcp" "7zr:p7zip" "hostapd" "lighttpd"
    "iwconfig:wireless-tools" "macchanger" "mdk4" "dsniff" "mdk3" "nmap" "openssl"
    "php-cgi" "pyrit" "xterm" "rfkill" "unzip" "route:net-tools"
    "fuser:psmisc" "killall:psmisc"
  )

    while ! installer_utils_check_dependencies requiredCLITools[@]; do
        if ! installer_utils_run_dependencies InstallerUtilsCheckDependencies[@]; then
            echo
            echo -e "${CRed}Dependency installation failed!$CClr"
            echo    "Press enter to retry, ctrl+c to exit..."
            read -r bullshit
        fi
    done
    if [ $FLUXIONMissingDependencies -eq 1 ]  && [ $FLUXIONSkipDependencies -eq 1 ];then
        echo -e "\n\n"
        format_center_literals "[ ${CSRed}Missing dependencies: try to install using ./fluxion.sh -i${CClr} ]"
        echo -e "$FormatCenterLiterals"; sleep 3

        exit 7
    fi

  echo -e "\\n\\n" # This echo is for spacing
}

fluxion_shutdown() {
  if [ $FLUXIONDebug ]; then return 1; fi

  # Show the header if the subroutine has already been loaded.
  if type -t fluxion_header &> /dev/null; then
    fluxion_header
  fi

  echo -e "$CWht[$CRed-$CWht]$CRed $FLUXIONCleanupAndClosingNotice$CClr"

  # Get running processes we might have to kill before exiting.
  local processes
  readarray processes < <(ps -A)

  # Currently, fluxion is only responsible for killing airodump-ng, since
  # fluxion explicitly uses it to scan for candidate target access points.
  # NOTICE: Processes started by subscripts, such as an attack script,
  # MUST BE TERMINATED BY THAT SCRIPT in the subscript's abort handler.
  local -r targets=("airodump-ng")

  local targetID # Program identifier/title
  for targetID in "${targets[@]}"; do
    # Get PIDs of all programs matching targetPID
    local targetPID
    targetPID=$(
      echo "${processes[@]}" | awk '$4~/'"$targetID"'/{print $1}'
    )
    if [ ! "$targetPID" ]; then continue; fi
    echo -e "$CWht[$CRed-$CWht] `io_dynamic_output $FLUXIONKillingProcessNotice`"
    kill -s SIGKILL $targetPID &> $FLUXIONOutputDevice
  done

  # Assure changes are reverted if installer was activated.
  if [ "$PackageManagerCLT" ]; then
    echo -e "$CWht[$CRed-$CWht] "$(
      io_dynamic_output "$FLUXIONRestoringPackageManagerNotice"
    )"$CClr"
    # Notice: The package manager has already been restored at this point.
    # InstallerUtils assures the manager is restored after running operations.
  fi

  # If allocated interfaces exist, deallocate them now.
  if [ ${#FluxionInterfaces[@]} -gt 0 ]; then
    local interface
    for interface in "${!FluxionInterfaces[@]}"; do
      # Only deallocate fluxion or airmon-ng created interfaces.
      if [[ "$interface" == "flux"* || "$interface" == *"mon"* || "$interface" == "prism"* ]]; then
        fluxion_deallocate_interface $interface
      fi
    done
  fi

  echo -e "$CWht[$CRed-$CWht] $FLUXIONDisablingCleaningIPTablesNotice$CClr"
  if [ -f "$FLUXIONIPTablesBackup" ]; then
    iptables-restore <"$FLUXIONIPTablesBackup" \
      &> $FLUXIONOutputDevice
  else
    iptables --flush
    iptables --table nat --flush
    iptables --delete-chain
    iptables --table nat --delete-chain
  fi

  echo -e "$CWht[$CRed-$CWht] $FLUXIONRestoringTputNotice$CClr"
  tput cnorm

  if [ ! $FLUXIONDebug ]; then
    echo -e "$CWht[$CRed-$CWht] $FLUXIONDeletingFilesNotice$CClr"
    sandbox_remove_workfile "$FLUXIONWorkspacePath/*"
  fi

  if [ $FLUXIONWIKillProcesses ]; then
    echo -e "$CWht[$CRed-$CWht] $FLUXIONRestartingNetworkManagerNotice$CClr"

    # TODO: Add support for other network managers (wpa_supplicant?).
    if [ ! -x "$(command -v systemctl)" ]; then
        if [ -x "$(command -v service)" ];then
        service network-manager restart &> $FLUXIONOutputDevice &
        service networkmanager restart &> $FLUXIONOutputDevice &
        service networking restart &> $FLUXIONOutputDevice &
      fi
    else
      systemctl restart network-manager.service &> $FLUXIONOutputDevice &
    fi
  fi

  echo -e "$CWht[$CGrn+$CWht] $CGrn$FLUXIONCleanupSuccessNotice$CClr"
  echo -e "$CWht[$CGrn+$CWht] $CGry$FLUXIONThanksSupportersNotice$CClr"

  sleep 3

  clear

  exit 0
}


# ============================================================ #
# ================= < Handler Subroutines > ================== #
# ============================================================ #
# Delete log only in Normal Mode !
fluxion_conditional_clear() {
  # Clear iff we're not in debug mode
  if [ ! $FLUXIONDebug ]; then clear; fi
}

fluxion_conditional_bail() {
  echo ${1:-"Something went wrong, whoops! (report this)"}
  sleep 5
  if [ ! $FLUXIONDebug ]; then
    fluxion_handle_exit
    return 1
  fi
  echo "Press any key to continue execution..."
  read -r bullshit
}

# ERROR Report only in Developer Mode
if [ $FLUXIONDebug ]; then
  fluxion_error_report() {
    echo "Exception caught @ line #$1"
  }

  trap 'fluxion_error_report $LINENO' ERR
fi

fluxion_handle_abort_attack() {
  if [ $(type -t stop_attack) ]; then
    stop_attack &> $FLUXIONOutputDevice
    unprep_attack &> $FLUXIONOutputDevice
  else
    echo "Attack undefined, can't stop anything..." > $FLUXIONOutputDevice
  fi

  fluxion_target_tracker_stop
}

# In case of abort signal, abort any attacks currently running.
trap fluxion_handle_abort_attack SIGABRT

fluxion_handle_exit() {
  fluxion_handle_abort_attack
  fluxion_shutdown
  exit 1
}

# In case of unexpected termination, run fluxion_shutdown.
trap fluxion_handle_exit SIGINT SIGHUP


fluxion_handle_target_change() {
  echo "Target change signal received!" > $FLUXIONOutputDevice

  local targetInfo
  readarray -t targetInfo < <(more "$FLUXIONWorkspacePath/target_info.txt")

  FluxionTargetMAC=${targetInfo[0]}
  FluxionTargetSSID=${targetInfo[1]}
  FluxionTargetChannel=${targetInfo[2]}

  FluxionTargetSSIDClean=$(fluxion_target_normalize_SSID)

  if ! stop_attack; then
    fluxion_conditional_bail "Target tracker failed to stop attack."
  fi

  if ! unprep_attack; then
    fluxion_conditional_bail "Target tracker failed to unprep attack."
  fi

  if ! load_attack "$FLUXIONPath/attacks/$FluxionAttack/attack.conf"; then
    fluxion_conditional_bail "Target tracker failed to load attack."
  fi

  if ! prep_attack; then
    fluxion_conditional_bail "Target tracker failed to prep attack."
  fi

  if ! fluxion_run_attack; then
    fluxion_conditional_bail "Target tracker failed to start attack."
  fi
}

# If target monitoring enabled, act on changes.
trap fluxion_handle_target_change SIGALRM


# ============================================================ #
# =============== < Resolution & Positioning > =============== #
# ============================================================ #
fluxion_set_resolution() { # Windows + Resolution

  # Get dimensions
  # Verify this works on Kali before commiting.
  # shopt -s checkwinsize; (:;:)
  # SCREEN_SIZE_X="$LINES"
  # SCREEN_SIZE_Y="$COLUMNS"

  SCREEN_SIZE=$(xdpyinfo | grep dimension | awk '{print $4}' | tr -d "(")
  SCREEN_SIZE_X=$(printf '%.*f\n' 0 $(echo $SCREEN_SIZE | sed -e s'/x/ /'g | awk '{print $1}'))
  SCREEN_SIZE_Y=$(printf '%.*f\n' 0 $(echo $SCREEN_SIZE | sed -e s'/x/ /'g | awk '{print $2}'))

  # Calculate proportional windows
  if hash bc ;then
    PROPOTION=$(echo $(awk "BEGIN {print $SCREEN_SIZE_X/$SCREEN_SIZE_Y}")/1 | bc)
    NEW_SCREEN_SIZE_X=$(echo $(awk "BEGIN {print $SCREEN_SIZE_X/$FLUXIONWindowRatio}")/1 | bc)
    NEW_SCREEN_SIZE_Y=$(echo $(awk "BEGIN {print $SCREEN_SIZE_Y/$FLUXIONWindowRatio}")/1 | bc)

    NEW_SCREEN_SIZE_BIG_X=$(echo $(awk "BEGIN {print 1.5*$SCREEN_SIZE_X/$FLUXIONWindowRatio}")/1 | bc)
    NEW_SCREEN_SIZE_BIG_Y=$(echo $(awk "BEGIN {print 1.5*$SCREEN_SIZE_Y/$FLUXIONWindowRatio}")/1 | bc)

    SCREEN_SIZE_MID_X=$(echo $(($SCREEN_SIZE_X + ($SCREEN_SIZE_X - 2 * $NEW_SCREEN_SIZE_X) / 2)))
    SCREEN_SIZE_MID_Y=$(echo $(($SCREEN_SIZE_Y + ($SCREEN_SIZE_Y - 2 * $NEW_SCREEN_SIZE_Y) / 2)))

    # Upper windows
    TOPLEFT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+0+0"
    TOPRIGHT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y-0+0"
    TOP="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+$SCREEN_SIZE_MID_X+0"

    # Lower windows
    BOTTOMLEFT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+0-0"
    BOTTOMRIGHT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y-0-0"
    BOTTOM="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+$SCREEN_SIZE_MID_X-0"

    # Y mid
    LEFT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+0-$SCREEN_SIZE_MID_Y"
    RIGHT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y-0+$SCREEN_SIZE_MID_Y"

    # Big
    TOPLEFTBIG="-geometry $NEW_SCREEN_SIZE_BIG_Xx$NEW_SCREEN_SIZE_BIG_Y+0+0"
    TOPRIGHTBIG="-geometry $NEW_SCREEN_SIZE_BIG_Xx$NEW_SCREEN_SIZE_BIG_Y-0+0"
  fi
}


# ============================================================ #
# ================= < Sequencing Framework > ================= #
# ============================================================ #
# The following lists some problems with the framework's design.
# The list below is a list of DESIGN FLAWS, not framework bugs.
# * Sequenced undo instructions' return value is being ignored.
# * A global is generated for every new namespace being used.
# * It uses eval too much, but it's bash, so that's not so bad.
# TODO: Try to fix this or come up with a better alternative.
declare -rA FLUXIONUndoable=( \
  ["set"]="unset" \
  ["prep"]="unprep" \
  ["run"]="halt" \
  ["start"]="stop" \
)

# Yes, I know, the identifiers are fucking ugly. If only we had
# some type of mangling with bash identifiers, that'd be great.
fluxion_do() {
  if [ ${#@} -lt 2 ]; then return -1; fi

  local -r __fluxion_do__namespace=$1
  local -r __fluxion_do__identifier=$2

  # Notice, the instruction will be adde to the Do Log
  # regardless of whether it succeeded or failed to execute.
  eval FXDLog_$__fluxion_do__namespace+=\("$__fluxion_do__identifier"\)
  eval ${__fluxion_do__namespace}_$__fluxion_do__identifier "${@:3}"
  return $?
}

fluxion_undo() {
  if [ ${#@} -ne 1 ]; then return -1; fi

  local -r __fluxion_undo__namespace=$1

  # Removed read-only due to local constant shadowing bug.
  # I've reported the bug, we can add it when fixed.
  eval local __fluxion_undo__history=\("\${FXDLog_$__fluxion_undo__namespace[@]}"\)

  eval echo \$\{FXDLog_$__fluxion_undo__namespace[@]\} \
    > $FLUXIONOutputDevice

  local __fluxion_undo__i
  for (( __fluxion_undo__i=${#__fluxion_undo__history[@]}; \
    __fluxion_undo__i > 0; __fluxion_undo__i-- )); do
    local __fluxion_undo__instruction=${__fluxion_undo__history[__fluxion_undo__i-1]}
    local __fluxion_undo__command=${__fluxion_undo__instruction%%_*}
    local __fluxion_undo__identifier=${__fluxion_undo__instruction#*_}

    echo "Do ${FLUXIONUndoable["$__fluxion_undo__command"]}_$__fluxion_undo__identifier" \
      > $FLUXIONOutputDevice
    if eval ${__fluxion_undo__namespace}_${FLUXIONUndoable["$__fluxion_undo__command"]}_$__fluxion_undo__identifier; then
      echo "Undo-chain succeded." > $FLUXIONOutputDevice
      eval FXDLog_$__fluxion_undo__namespace=\("${__fluxion_undo__history[@]::$__fluxion_undo__i}"\)
      eval echo History\: \$\{FXDLog_$__fluxion_undo__namespace[@]\} \
        > $FLUXIONOutputDevice
      return 0
    fi
  done

  return -2 # The undo-chain failed.
}

fluxion_done() {
  if [ ${#@} -ne 1 ]; then return -1; fi

  local -r __fluxion_done__namespace=$1

  eval "FluxionDone=\${FXDLog_$__fluxion_done__namespace[-1]}"

  if [ ! "$FluxionDone" ]; then return 1; fi
}

fluxion_done_reset() {
  if [ ${#@} -ne 1 ]; then return -1; fi

  local -r __fluxion_done_reset__namespace=$1

  eval FXDLog_$__fluxion_done_reset__namespace=\(\)
}

fluxion_do_sequence() {
  if [ ${#@} -ne 2 ]; then return 1; fi

  # TODO: Implement an alternative, better method of doing
  # what this subroutine does, maybe using for-loop iteFLUXIONWindowRation.
  # The for-loop implementation must support the subroutines
  # defined above, including updating the namespace tracker.

  local -r __fluxion_do_sequence__namespace=$1

  # Removed read-only due to local constant shadowing bug.
  # I've reported the bug, we can add it when fixed.
  local __fluxion_do_sequence__sequence=("${!2}")

  if [ ${#__fluxion_do_sequence__sequence[@]} -eq 0 ]; then
    return -2
  fi

  local -A __fluxion_do_sequence__index=()

  local i
  for i in $(seq 0 $((${#__fluxion_do_sequence__sequence[@]} - 1))); do
    __fluxion_do_sequence__index["${__fluxion_do_sequence__sequence[i]}"]=$i
  done

  # Start sequence with the first instruction available.
  local __fluxion_do_sequence__instructionIndex=0
  local __fluxion_do_sequence__instruction=${__fluxion_do_sequence__sequence[0]}
  while [ "$__fluxion_do_sequence__instruction" ]; do
    if ! fluxion_do $__fluxion_do_sequence__namespace $__fluxion_do_sequence__instruction; then
      if ! fluxion_undo $__fluxion_do_sequence__namespace; then
        return -2
      fi

      # Synchronize the current instruction's index by checking last.
      if ! fluxion_done $__fluxion_do_sequence__namespace; then
        return -3;
      fi

      __fluxion_do_sequence__instructionIndex=${__fluxion_do_sequence__index["$FluxionDone"]}

      if [ ! "$__fluxion_do_sequence__instructionIndex" ]; then
        return -4
      fi
    else
      let __fluxion_do_sequence__instructionIndex++
    fi

    __fluxion_do_sequence__instruction=${__fluxion_do_sequence__sequence[$__fluxion_do_sequence__instructionIndex]}
    echo "Running next: $__fluxion_do_sequence__instruction" \
      > $FLUXIONOutputDevice
  done
}


# ============================================================ #
# ================= < Load All Subroutines > ================= #
# ============================================================ #
fluxion_header() {
  format_apply_autosize "[%*s]\n"
  local verticalBorder=$FormatApplyAutosize

  format_apply_autosize "[%*s${CSRed}FLUXION $FLUXIONVersion${CSWht}.${CSBlu}$FLUXIONRevision$CSRed    <$CIRed F${CIYel}luxion$CIRed I${CIYel}s$CIRed T${CIYel}he$CIRed F${CIYel}uture$CClr$CSYel >%*s$CSBlu]\n"
  local headerTextFormat="$FormatApplyAutosize"

  fluxion_conditional_clear

  echo -e "$(printf "$CSRed$verticalBorder" "" | sed -r "s/ /~/g")"
  printf "$CSRed$verticalBorder" ""
  printf "$headerTextFormat" "" ""
  printf "$CSBlu$verticalBorder" ""
  echo -e "$(printf "$CSBlu$verticalBorder" "" | sed -r "s/ /~/g")$CClr"
  echo
  echo
}

# ======================= < Language > ======================= #
fluxion_unset_language() {
  FluxionLanguage=""

  if [ "$FLUXIONPreferencesFile" ]; then
    sed -i.backup "/FluxionLanguage=.\+/ d" "$FLUXIONPreferencesFile"
  fi
}

fluxion_set_language() {
  if [ ! "$FluxionLanguage" ]; then
    # Get all languages available.
    local languageCodes
    readarray -t languageCodes < <(ls -1 language | sed -E 's/\.sh//')

    local languages
    readarray -t languages < <(
      head -n 3 language/*.sh |
      grep -E "^# native: " |
      sed -E 's/# \w+: //'
    )

    io_query_format_fields "$FLUXIONVLine Select your language" \
      "\t$CRed[$CSYel%d$CClr$CRed]$CClr %s / %s\n" \
      languageCodes[@] languages[@]

    FluxionLanguage=${IOQueryFormatFields[0]}

    echo # Do not remove.
  fi

  # Check if all language files are present for the selected language.
  find -type d -name language | while read language_dir; do
    if [ ! -e "$language_dir/${FluxionLanguage}.sh" ]; then
      echo -e "$FLUXIONVLine ${CYel}Warning${CClr}, missing language file:"
      echo -e "\t$language_dir/${FluxionLanguage}.sh"
      return 1
    fi
  done

  if [ $? -eq 1 ]; then # If a file is missing, fall back to english.
    echo -e "\n\n$FLUXIONVLine Falling back to English..."; sleep 5
    FluxionLanguage="en"
  fi

  source "$FLUXIONPath/language/$FluxionLanguage.sh"

  if [ "$FLUXIONPreferencesFile" ]; then
    if more $FLUXIONPreferencesFile | \
      grep -q "FluxionLanguage=.\+" &> /dev/null; then
      sed -r "s/FluxionLanguage=.+/FluxionLanguage=$FluxionLanguage/g" \
      -i.backup "$FLUXIONPreferencesFile"
    else
      echo "FluxionLanguage=$FluxionLanguage" >> "$FLUXIONPreferencesFile"
    fi
  fi
}

# ====================== < Interfaces > ====================== #
declare -A FluxionInterfaces=() # Global interfaces' registry.

fluxion_deallocate_interface() { # Release interfaces
  if [ ! "$1" ] || ! interface_is_real $1; then return 1; fi

  local -r oldIdentifier=$1
  local -r newIdentifier=${FluxionInterfaces[$oldIdentifier]}

  # Assure the interface is in the allocation table.
  if [ ! "$newIdentifier" ]; then return 2; fi

  local interfaceIdentifier=$newIdentifier
  echo -e "$CWht[$CSRed-$CWht] "$(
    io_dynamic_output "$FLUXIONDeallocatingInterfaceNotice"
  )"$CClr"

  if interface_is_wireless $oldIdentifier; then
    # If interface was allocated by airmon-ng, deallocate with it.
    if [[ "$oldIdentifier" == *"mon"* || "$oldIdentifier" == "prism"* ]]; then
      if ! airmon-ng stop $oldIdentifier &> $FLUXIONOutputDevice; then
        return 4
      fi
    else
      # Attempt deactivating monitor mode on the interface.
      if ! interface_set_mode $oldIdentifier managed; then
        return 3
      fi

      # Attempt to restore the original interface identifier.
      if ! interface_reidentify "$oldIdentifier" "$newIdentifier"; then
        return 5
      fi
    fi
  fi

  # Once successfully renamed, remove from allocation table.
  unset FluxionInterfaces[$oldIdentifier]
  unset FluxionInterfaces[$newIdentifier]
}

# Parameters: <interface_identifier>
# ------------------------------------------------------------ #
# Return 1: No interface identifier was passed.
# Return 2: Interface identifier given points to no interface.
# Return 3: Unable to determine interface's driver.
# Return 4: Fluxion failed to reidentify interface.
# Return 5: Interface allocation failed (identifier missing).
fluxion_allocate_interface() { # Reserve interfaces
  if [ ! "$1" ]; then return 1; fi

  local -r identifier=$1

  # If the interface is already in allocation table, we're done.
  if [ "${FluxionInterfaces[$identifier]+x}" ]; then
    return 0
  fi

  if ! interface_is_real $identifier; then return 2; fi


  local interfaceIdentifier=$identifier
  echo -e "$CWht[$CSGrn+$CWht] "$(
    io_dynamic_output "$FLUXIONAllocatingInterfaceNotice"
  )"$CClr"


  if interface_is_wireless $identifier; then
    # Unblock wireless interfaces to make them available.
    echo -e "$FLUXIONVLine $FLUXIONUnblockingWINotice"
    rfkill unblock all &> $FLUXIONOutputDevice

    if [ "$FLUXIONWIReloadDriver" ]; then
      # Get selected interface's driver details/info-descriptor.
      echo -e "$FLUXIONVLine $FLUXIONGatheringWIInfoNotice"

      if ! interface_driver "$identifier"; then
        echo -e "$FLUXIONVLine$CRed $FLUXIONUnknownWIDriverError"
        sleep 3
        return 3
      fi

      # Notice: This local is function-scoped, not block-scoped.
      local -r driver="$InterfaceDriver"

      # Unload the driver module from the kernel.
      rmmod -f $driver &> $FLUXIONOutputDevice

      # Wait while interface becomes unavailable.
      echo -e "$FLUXIONVLine "$(
        io_dynamic_output $FLUXIONUnloadingWIDriverNotice
      )
      while interface_physical "$identifier"; do
        sleep 1
      done
    fi

    if [ "$FLUXIONWIKillProcesses" ]; then
      # Get list of potentially troublesome programs.
      echo -e "$FLUXIONVLine $FLUXIONFindingConflictingProcessesNotice"

      # Kill potentially troublesome programs.
      echo -e "$FLUXIONVLine $FLUXIONKillingConflictingProcessesNotice"

      # TODO: Make the loop below airmon-ng independent.
      # Maybe replace it with a list of network-managers?
      # WARNING: Version differences could break code below.
      for program in "$(airmon-ng check | awk 'NR>6{print $2}')"; do
        killall "$program" &> $FLUXIONOutputDevice
      done
    fi

    if [ "$FLUXIONWIReloadDriver" ]; then
      # Reload the driver module into the kernel.
      modprobe "$driver" &> $FLUXIONOutputDevice

      # Wait while interface becomes available.
      echo -e "$FLUXIONVLine "$(
        io_dynamic_output $FLUXIONLoadingWIDriverNotice
      )
      while ! interface_physical "$identifier"; do
        sleep 1
      done
    fi

    # Set wireless flag to prevent having to re-query.
    local -r allocatingWirelessInterface=1
  fi

  # If we're using the interface library, reidentify now.
  # If usuing airmon-ng, let airmon-ng rename the interface.
  if [ ! $FLUXIONAirmonNG ]; then
    echo -e "$FLUXIONVLine $FLUXIONReidentifyingInterface"

    # Prevent interface-snatching by renaming the interface.
    if [ $allocatingWirelessInterface ]; then
      # Get next wireless interface to add to FluxionInterfaces global.
      fluxion_next_assignable_interface fluxwl
    else
      # Get next ethernet interface to add to FluxionInterfaces global.
      fluxion_next_assignable_interface fluxet
    fi

    interface_reidentify $identifier $FluxionNextAssignableInterface

    if [ $? -ne 0 ]; then # If reidentifying failed, abort immediately.
      return 4
    fi
  fi

  if [ $allocatingWirelessInterface ]; then
    # Activate wireless interface monitor mode and save identifier.
    echo -e "$FLUXIONVLine $FLUXIONStartingWIMonitorNotice"

    # TODO: Consider the airmon-ng flag is set, monitor mode is
    # already enabled on the interface being allocated, and the
    # interface identifier is something non-airmon-ng standard.
    # The interface could already be in use by something else.
    # Snatching or crashing interface issues could occur.

    # NOTICE: Conditionals below populate newIdentifier on success.
    if [ $FLUXIONAirmonNG ]; then
      local -r newIdentifier=$(
        airmon-ng start $identifier |
        grep "monitor .* enabled" |
        grep -oP "wl[a-zA-Z0-9]+mon|mon[0-9]+|prism[0-9]+"
      )
    else
      # Attempt activating monitor mode on the interface.
      if interface_set_mode $FluxionNextAssignableInterface monitor; then
        # Register the new identifier upon consecutive successes.
        local -r newIdentifier=$FluxionNextAssignableInterface
      else
        # If monitor-mode switch fails, undo rename and abort.
        interface_reidentify $FluxionNextAssignableInterface $identifier
      fi
    fi
  fi

  # On failure to allocate the interface, we've got to abort.
  # Notice: If the interface was already in monitor mode and
  # airmon-ng is activated, WE didn't allocate the interface.
  if [ ! "$newIdentifier" -o "$newIdentifier" = "$oldIdentifier" ]; then
    echo -e "$FLUXIONVLine $FLUXIONInterfaceAllocationFailedError"
    sleep 3
    return 5
  fi

  # Register identifiers to allocation hash table.
  FluxionInterfaces[$newIdentifier]=$identifier
  FluxionInterfaces[$identifier]=$newIdentifier

  echo -e "$FLUXIONVLine $FLUXIONInterfaceAllocatedNotice"
  sleep 3

  # Notice: Interfaces are accessed with their original identifier
  # as the key for the global FluxionInterfaces hash/map/dictionary.
}

# Parameters: <interface_prefix>
# Description: Prints next available assignable interface name.
# ------------------------------------------------------------ #
fluxion_next_assignable_interface() {
  # Find next available interface by checking global.
  local -r prefix=$1
  local index=0
  while [ "${FluxionInterfaces[$prefix$index]}" ]; do
    let index++
  done
  FluxionNextAssignableInterface="$prefix$index"
}

# Parameters: <interfaces:lambda> [<query>]
# Note: The interfaces lambda must print an interface per line.
# ------------------------------------------------------------ #
# Return -1: Go back
# Return  1: Missing interfaces lambda identifier (not passed).
fluxion_get_interface() {
  if ! type -t "$1" &> /dev/null; then return 1; fi

  if [ "$2" ]; then
    local -r interfaceQuery="$2"
  else
    local -r interfaceQuery=$FLUXIONInterfaceQuery
  fi

  while true; do
    local candidateInterfaces
    readarray -t candidateInterfaces < <($1)
    local interfacesAvailable=()
    local interfacesAvailableInfo=()
    local interfacesAvailableColor=()
    local interfacesAvailableState=()

    # Gather information from all available interfaces.
    local candidateInterface
    for candidateInterface in "${candidateInterfaces[@]}"; do
      if [ ! "$candidateInterface" ]; then
        local skipOption=1
        continue
      fi

      interface_chipset "$candidateInterface"
      interfacesAvailableInfo+=("$InterfaceChipset")

      # If it has already been allocated, we can use it at will.
      local candidateInterfaceAlt=${FluxionInterfaces["$candidateInterface"]}
      if [ "$candidateInterfaceAlt" ]; then
        interfacesAvailable+=("$candidateInterfaceAlt")

        interfacesAvailableColor+=("$CGrn")
        interfacesAvailableState+=("[*]")
      else
        interfacesAvailable+=("$candidateInterface")

        interface_state "$candidateInterface"

        if [ "$InterfaceState" = "up" ]; then
          interfacesAvailableColor+=("$CPrp")
          interfacesAvailableState+=("[-]")
        else
          interfacesAvailableColor+=("$CClr")
          interfacesAvailableState+=("[+]")
        fi
      fi
    done

    # If only one interface exists and it's not unavailable, choose it.
    if [ "${#interfacesAvailable[@]}" -eq 1 -a \
      "${interfacesAvailableState[0]}" != "[-]" -a \
      "$skipOption" == "" ]; then FluxionInterfaceSelected="${interfacesAvailable[0]}"
      FluxionInterfaceSelectedState="${interfacesAvailableState[0]}"
      FluxionInterfaceSelectedInfo="${interfacesAvailableInfo[0]}"
      break
    else
      if [ $skipOption ]; then
        interfacesAvailable+=("$FLUXIONGeneralSkipOption")
        interfacesAvailableColor+=("$CClr")
      fi

      interfacesAvailable+=(
        "$FLUXIONGeneralRepeatOption"
        "$FLUXIONGeneralBackOption"
      )

      interfacesAvailableColor+=(
        "$CClr"
        "$CClr"
      )

      format_apply_autosize \
        "$CRed[$CSYel%1d$CClr$CRed]%b %-8b %3s$CClr %-*.*s\n"

      io_query_format_fields \
        "$FLUXIONVLine $interfaceQuery" "$FormatApplyAutosize" \
        interfacesAvailableColor[@] interfacesAvailable[@] \
        interfacesAvailableState[@] interfacesAvailableInfo[@]

      echo

      case "${IOQueryFormatFields[1]}" in
        "$FLUXIONGeneralSkipOption")
          FluxionInterfaceSelected=""
          FluxionInterfaceSelectedState=""
          FluxionInterfaceSelectedInfo=""
          return 0;;
        "$FLUXIONGeneralRepeatOption") continue;;
        "$FLUXIONGeneralBackOption") return -1;;
        *)
          FluxionInterfaceSelected="${IOQueryFormatFields[1]}"
          FluxionInterfaceSelectedState="${IOQueryFormatFields[2]}"
          FluxionInterfaceSelectedInfo="${IOQueryFormatFields[3]}"
          break;;
      esac
    fi
  done
}


# ============== < Fluxion Target Subroutines > ============== #
# Parameters: interface [ channel(s) [ band(s) ] ]
# ------------------------------------------------------------ #
# Return 1: Missing monitor interface.
# Return 2: Xterm failed to start airmon-ng.
# Return 3: Invalid capture file was generated.
# Return 4: No candidates were detected.
fluxion_target_get_candidates() {
  # Assure a valid wireless interface for scanning was given.
  if [ ! "$1" ] || ! interface_is_wireless "$1"; then return 1; fi

  echo -e "$FLUXIONVLine $FLUXIONStartingScannerNotice"
  echo -e "$FLUXIONVLine $FLUXIONStartingScannerTip"

  # Assure all previous scan results have been cleared.
  sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"

  #if [ "$FLUXIONAuto" ]; then
  #  sleep 30 && killall xterm &
  #fi

  # Begin scanner and output all results to "dump-01.csv."
if ! xterm -title "$FLUXIONScannerHeader" $TOPLEFTBIG \
    -bg "#000000" -fg "#FFFFFF" -e \
    "airodump-ng -Mat WPA "${2:+"--channel $2"}" "${3:+"--band $3"}" -w \"$FLUXIONWorkspacePath/dump\" $1" 2> $FLUXIONOutputDevice; then
    echo -e "$FLUXIONVLine$CRed $FLUXIONGeneralXTermFailureError"
    sleep 5
    return 2
fi

  # Sanity check the capture files generated by the scanner.
  # If the file doesn't exist, or if it's empty, abort immediately.
  if [ ! -f "$FLUXIONWorkspacePath/dump-01.csv" -o \
    ! -s "$FLUXIONWorkspacePath/dump-01.csv" ]; then
    sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"
    return 3
  fi

  # Syntheize scan opeFLUXIONWindowRation results from output file "dump-01.csv."
  echo -e "$FLUXIONVLine $FLUXIONPreparingScannerResultsNotice"
  # WARNING: The code below may break with different version of airmon-ng.
  # The times matching operator "{n}" isn't supported by mawk (alias awk).
  # readarray FLUXIONTargetCandidates < <(
  #   gawk -F, 'NF==15 && $1~/([A-F0-9]{2}:){5}[A-F0-9]{2}/ {print $0}'
  #   $FLUXIONWorkspacePath/dump-01.csv
  # )
  # readarray FLUXIONTargetCandidatesClients < <(
  #   gawk -F, 'NF==7 && $1~/([A-F0-9]{2}:){5}[A-F0-9]{2}/ {print $0}'
  #   $FLUXIONWorkspacePath/dump-01.csv
  # )
  local -r matchMAC="([A-F0-9][A-F0-9]:)+[A-F0-9][A-F0-9]"
  readarray FluxionTargetCandidates < <(
    awk -F, "NF==15 && length(\$1)==17 && \$1~/$matchMAC/ {print \$0}" \
    "$FLUXIONWorkspacePath/dump-01.csv"
  )
  readarray FluxionTargetCandidatesClients < <(
    awk -F, "NF==7 && length(\$1)==17 && \$1~/$matchMAC/ {print \$0}" \
    "$FLUXIONWorkspacePath/dump-01.csv"
  )

  # Cleanup the workspace to prevent potential bugs/conflicts.
  sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"

  if [ ${#FluxionTargetCandidates[@]} -eq 0 ]; then
    echo -e "$FLUXIONVLine $FLUXIONScannerDetectedNothingNotice"
    sleep 3
    return 4
  fi
}


fluxion_get_target() {
  # Assure a valid wireless interface for scanning was given.
  if [ ! "$1" ] || ! interface_is_wireless "$1"; then return 1; fi

  local -r interface=$1

  local choices=( \
    "$FLUXIONScannerChannelOptionAll (2.4GHz)" \
    "$FLUXIONScannerChannelOptionAll (5GHz)" \
    "$FLUXIONScannerChannelOptionAll (2.4GHz & 5Ghz)" \
    "$FLUXIONScannerChannelOptionSpecific" "$FLUXIONGeneralBackOption"
  )

  io_query_choice "$FLUXIONScannerChannelQuery" choices[@]

  echo

  case "$IOQueryChoice" in
    "$FLUXIONScannerChannelOptionAll (2.4GHz)")
      fluxion_target_get_candidates $interface "" "bg";;

    "$FLUXIONScannerChannelOptionAll (5GHz)")
      fluxion_target_get_candidates $interface "" "a";;

    "$FLUXIONScannerChannelOptionAll (2.4GHz & 5Ghz)")
      fluxion_target_get_candidates $interface "" "abg";;

    "$FLUXIONScannerChannelOptionSpecific")
      fluxion_header

      echo -e "$FLUXIONVLine $FLUXIONScannerChannelQuery"
      echo
      echo -e "     $FLUXIONScannerChannelSingleTip ${CBlu}6$CClr               "
      echo -e "     $FLUXIONScannerChannelMiltipleTip ${CBlu}1-5$CClr             "
      echo -e "     $FLUXIONScannerChannelMiltipleTip ${CBlu}1,2,5-7,11$CClr      "
      echo
      echo -ne "$FLUXIONPrompt"

      local channels
      read channels

      echo

      fluxion_target_get_candidates $interface $channels;;

    "$FLUXIONGeneralBackOption")
      return -1;;
  esac

  # Abort if errors occured while searching for candidates.
  if [ $? -ne 0 ]; then return 2; fi

  local candidatesMAC=()
  local candidatesClientsCount=()
  local candidatesChannel=()
  local candidatesSecurity=()
  local candidatesSignal=()
  local candidatesPower=()
  local candidatesESSID=()
  local candidatesColor=()

  # Gather information from all the candidates detected.
  # TODO: Clean up this for loop using a cleaner algorithm.
  # Maybe try using array appending & [-1] for last elements.
  for candidateAPInfo in "${FluxionTargetCandidates[@]}"; do
    # Strip candidate info from any extraneous spaces after commas.
    candidateAPInfo=$(echo "$candidateAPInfo" | sed -r "s/,\s*/,/g")

    local i=${#candidatesMAC[@]}

    candidatesMAC[i]=$(echo "$candidateAPInfo" | cut -d , -f 1)
    candidatesClientsCount[i]=$(
      echo "${FluxionTargetCandidatesClients[@]}" |
      grep -c "${candidatesMAC[i]}"
    )
    candidatesChannel[i]=$(echo "$candidateAPInfo" | cut -d , -f 4)
    candidatesSecurity[i]=$(echo "$candidateAPInfo" | cut -d , -f 6)
    candidatesPower[i]=$(echo "$candidateAPInfo" | cut -d , -f 9)
    candidatesColor[i]=$(
      [ ${candidatesClientsCount[i]} -gt 0 ] && echo $CGrn || echo $CClr
    )

    # Parse any non-ascii characters by letting bash handle them.
    # Escape all single quotes in ESSID and let bash's $'...' handle it.
    local sanitizedESSID=$(
      echo "${candidateAPInfo//\'/\\\'}" | cut -d , -f 14
    )
    candidatesESSID[i]=$(eval "echo \$'$sanitizedESSID'")

    local power=${candidatesPower[i]}
    if [ $power -eq -1 ]; then
      # airodump-ng's man page says -1 means unsupported value.
      candidatesQuality[i]="??"
    elif [ $power -le $FLUXIONNoiseFloor ]; then
      candidatesQuality[i]=0
    elif [ $power -gt $FLUXIONNoiseCeiling ]; then
      candidatesQuality[i]=100
    else
      # Bash doesn't support floating point division, work around it...
      # Q = ((P - F) / (C - F)); Q-quality, P-power, F-floor, C-Ceiling.
      candidatesQuality[i]=$(( \
        (${candidatesPower[i]} * 10 - $FLUXIONNoiseFloor * 10) / \
        (($FLUXIONNoiseCeiling - $FLUXIONNoiseFloor) / 10) \
      ))
    fi
  done

  format_center_literals "WIFI LIST"
  local -r headerTitle="$FormatCenterLiterals\n\n"

  format_apply_autosize "$CRed[$CSYel ** $CClr$CRed]$CClr %-*.*s %4s %3s %3s %2s %-8.8s %18s\n"
  local -r headerFields=$(
    printf "$FormatApplyAutosize" \
      "ESSID" "QLTY" "PWR" "STA" "CH" "SECURITY" "BSSID"
  )

  format_apply_autosize "$CRed[$CSYel%03d$CClr$CRed]%b %-*.*s %3s%% %3s %3d %2s %-8.8s %18s\n"
  io_query_format_fields "$headerTitle$headerFields" \
   "$FormatApplyAutosize" \
    candidatesColor[@] \
    candidatesESSID[@] \
    candidatesQuality[@] \
    candidatesPower[@] \
    candidatesClientsCount[@] \
    candidatesChannel[@] \
    candidatesSecurity[@] \
    candidatesMAC[@]

  echo

  FluxionTargetMAC=${IOQueryFormatFields[7]}
  FluxionTargetSSID=${IOQueryFormatFields[1]}
  FluxionTargetChannel=${IOQueryFormatFields[5]}

  FluxionTargetEncryption=${IOQueryFormatFields[6]}

  FluxionTargetMakerID=${FluxionTargetMAC:0:8}
  FluxionTargetMaker=$(
    macchanger -l |
    grep ${FluxionTargetMakerID,,} 2> $FLUXIONOutputDevice |
    cut -d ' ' -f 5-
  )

  FluxionTargetSSIDClean=$(fluxion_target_normalize_SSID)

  # We'll change a single hex digit from the target AP's MAC address.
  # This new MAC address will be used as the rogue AP's MAC address.
  local -r rogueMACHex=$(printf %02X $((0x${FluxionTargetMAC:13:1} + 1)))
  FluxionTargetRogueMAC="${FluxionTargetMAC::13}${rogueMACHex:1:1}${FluxionTargetMAC:14:4}"
}

fluxion_target_normalize_SSID() {
  # Sanitize network ESSID to make it safe for manipulation.
  # Notice: Why remove these? Some smartass might decide to name their
  # network "; rm -rf / ;". If the string isn't sanitized accidentally
  # shit'll hit the fan and we'll have an extremly distressed user.
  # Replacing ' ', '/', '.', '~', '\' with '_'
  echo "$FluxionTargetSSID" | sed -r 's/( |\/|\.|\~|\\)+/_/g'
}

fluxion_target_show() {
  format_apply_autosize "%*s$CBlu%7s$CClr: %-32s%*s\n"

  local colorlessFormat="$FormatApplyAutosize"
  local colorfullFormat=$(
    echo "$colorlessFormat" | sed -r 's/%-32s/%-32b/g'
  )

  printf "$colorlessFormat" "" "ESSID" "\"${FluxionTargetSSID:-[N/A]}\" / ${FluxionTargetEncryption:-[N/A]}" ""
  printf "$colorlessFormat" "" "Channel" " ${FluxionTargetChannel:-[N/A]}" ""
  printf "$colorfullFormat" "" "BSSID" " ${FluxionTargetMAC:-[N/A]} ($CYel${FluxionTargetMaker:-[N/A]}$CClr)" ""

  echo
}

fluxion_target_tracker_daemon() {
  if [ ! "$1" ]; then return 1; fi # Assure we've got fluxion's PID.

  readonly fluxionPID=$1
  readonly monitorTimeout=10 # In seconds.
  readonly capturePath="$FLUXIONWorkspacePath/tracker_capture"

  if [ \
    -z "$FluxionTargetMAC" -o \
    -z "$FluxionTargetSSID" -o \
    -z "$FluxionTargetChannel" ]; then
    return 2 # If we're missing target information, we can't track properly.
  fi

  while true; do
    echo "[T-Tracker] Captor listening for $monitorTimeout seconds..."
    timeout --preserve-status $monitorTimeout airodump-ng -aw "$capturePath" \
      -d "$FluxionTargetMAC" $FluxionTargetTrackerInterface &> /dev/null
    local error=$? # Catch the returned status error code.

    if [ $error -ne 0 ]; then # If any error was encountered, abort!
      echo -e "[T-Tracker] ${CRed}Error:$CClr Operation aborted (code: $error)!"
      break
    fi

    local targetInfo=$(head -n 3 "$capturePath-01.csv" | tail -n 1)
    sandbox_remove_workfile "$capturePath-*"

    local targetChannel=$(
      echo "$targetInfo" | awk -F, '{gsub(/ /, "", $4); print $4}'
    )

    echo "[T-Tracker] $targetInfo"

    if [ "$targetChannel" -ne "$FluxionTargetChannel" ]; then
      echo "[T-Tracker] Target channel change detected!"
      FluxionTargetChannel=$targetChannel
      break
    fi

    # NOTE: We might also want to check for SSID changes here, assuming the only
    # thing that remains constant is the MAC address. The problem with that is
    # that airodump-ng has some serious problems with unicode, apparently.
    # Try feeding it an access point with Chinese characters and check the .csv.
  done

  # Save/overwrite the new target information to the workspace for retrival.
  echo "$FluxionTargetMAC" > "$FLUXIONWorkspacePath/target_info.txt"
  echo "$FluxionTargetSSID" >> "$FLUXIONWorkspacePath/target_info.txt"
  echo "$FluxionTargetChannel" >> "$FLUXIONWorkspacePath/target_info.txt"

  # NOTICE: Using different signals for different things is a BAD idea.
  # We should use a single signal, SIGINT, to handle different situations.
  kill -s SIGALRM $fluxionPID # Signal fluxion a change was detected.

  sandbox_remove_workfile "$capturePath-*"
}

fluxion_target_tracker_stop() {
  if [ ! "$FluxionTargetTrackerDaemonPID" ]; then return 1; fi
  kill -s SIGABRT $FluxionTargetTrackerDaemonPID &> /dev/null
  FluxionTargetTrackerDaemonPID=""
}

fluxion_target_tracker_start() {
  if [ ! "$FluxionTargetTrackerInterface" ]; then return 1; fi

  fluxion_target_tracker_daemon $$ &> "$FLUXIONOutputDevice" &
  FluxionTargetTrackerDaemonPID=$!
}

fluxion_target_unset_tracker() {
  if [ ! "$FluxionTargetTrackerInterface" ]; then return 1; fi

  FluxionTargetTrackerInterface=""
}

fluxion_target_set_tracker() {
  if [ "$FluxionTargetTrackerInterface" ]; then
    echo "Tracker interface already set, skipping." > $FLUXIONOutputDevice
    return 0
  fi

  # Check if attack provides tracking interfaces, get & set one.
  if ! type -t attack_tracking_interfaces &> /dev/null; then
    echo "Tracker DOES NOT have interfaces available!" > $FLUXIONOutputDevice
    return 1
  fi

  if [ "$FluxionTargetTrackerInterface" == "" ]; then
    echo "Running get interface (tracker)." > $FLUXIONOutputDevice
    local -r interfaceQuery=$FLUXIONTargetTrackerInterfaceQuery
    local -r interfaceQueryTip=$FLUXIONTargetTrackerInterfaceQueryTip
    local -r interfaceQueryTip2=$FLUXIONTargetTrackerInterfaceQueryTip2
    if ! fluxion_get_interface attack_tracking_interfaces \
      "$interfaceQuery\n$FLUXIONVLine $interfaceQueryTip\n$FLUXIONVLine $interfaceQueryTip2"; then
      echo "Failed to get tracker interface!" > $FLUXIONOutputDevice
      return 2
    fi
    local selectedInterface=$FluxionInterfaceSelected
  else
    # Assume user passed one via the command line and move on.
    # If none was given we'll take care of that case below.
    local selectedInterface=$FluxionTargetTrackerInterface
    echo "Tracker interface passed via command line!" > $FLUXIONOutputDevice
  fi

  # If user skipped a tracker interface, move on.
  if [ ! "$selectedInterface" ]; then
    fluxion_target_unset_tracker
    return 0
  fi

  if ! fluxion_allocate_interface $selectedInterface; then
    echo "Failed to allocate tracking interface!" > $FLUXIONOutputDevice
    return 3
  fi

  echo "Successfully got tracker interface." > $FLUXIONOutputDevice
  FluxionTargetTrackerInterface=${FluxionInterfaces[$selectedInterface]}
}

fluxion_target_unset() {
  FluxionTargetMAC=""
  FluxionTargetSSID=""
  FluxionTargetChannel=""

  FluxionTargetEncryption=""

  FluxionTargetMakerID=""
  FluxionTargetMaker=""

  FluxionTargetSSIDClean=""

  FluxionTargetRogueMAC=""

  return 1 # To trigger undo-chain.
}

fluxion_target_set() {
  # Check if attack is targetted & set the attack target if so.
  if ! type -t attack_targetting_interfaces &> /dev/null; then
    return 1
  fi

  if [ \
    "$FluxionTargetSSID" -a \
    "$FluxionTargetMAC" -a \
    "$FluxionTargetChannel" \
  ]; then
    # If we've got a candidate target, ask user if we'll keep targetting it.

    fluxion_header
    fluxion_target_show
    echo
    echo -e  "$FLUXIONVLine $FLUXIONTargettingAccessPointAboveNotice"

    # TODO: This doesn't translate choices to the selected language.
    while ! echo "$choice" | grep -q "^[ynYN]$" &> /dev/null; do
      echo -ne "$FLUXIONVLine $FLUXIONContinueWithTargetQuery [Y/n] "
      local choice
      read choice
      if [ ! "$choice" ]; then break; fi
    done

    echo -ne "\n\n"

    if [ "${choice,,}" != "n" ]; then
      return 0
    fi
  elif [ \
    "$FluxionTargetSSID" -o \
    "$FluxionTargetMAC" -o \
    "$FluxionTargetChannel" \
  ]; then
    # TODO: Survey environment here to autofill missing fields.
    # In other words, if a user gives incomplete information, scan
    # the environment based on either the ESSID or BSSID, & autofill.
    echo -e "$FLUXIONVLine $FLUXIONIncompleteTargettingInfoNotice"
    sleep 3
  fi

  if ! fluxion_get_interface attack_targetting_interfaces \
    "$FLUXIONTargetSearchingInterfaceQuery"; then
    return 2
  fi

  if ! fluxion_allocate_interface $FluxionInterfaceSelected; then
    return 3
  fi

  if ! fluxion_get_target \
    ${FluxionInterfaces[$FluxionInterfaceSelected]}; then
    return 4
  fi
}


# =================== < Hash Subroutines > =================== #
# Parameters: <hash path> <bssid> <essid> [channel [encryption [maker]]]
fluxion_hash_verify() {
  if [ ${#@} -lt 3 ]; then return 1; fi

  local -r hashPath=$1
  local -r hashBSSID=$2
  local -r hashESSID=$3
  local -r hashChannel=$4
  local -r hashEncryption=$5
  local -r hashMaker=$6

  if [ ! -f "$hashPath" -o ! -s "$hashPath" ]; then
    echo -e "$FLUXIONVLine $FLUXIONHashFileDoesNotExistError"
    sleep 3
    return 2
  fi

  if [ "$FLUXIONAuto" ]; then
    local -r verifier="cowpatty"
  else
    fluxion_header

    echo -e "$FLUXIONVLine $FLUXIONHashVerificationMethodQuery"
    echo

    fluxion_target_show

    local choices=( \
      "$FLUXIONHashVerificationMethodPyritOption" \
      "$FLUXIONHashVerificationMethodAircrackOption" \
      "$FLUXIONHashVerificationMethodCowpattyOption" \
      "$FLUXIONGeneralBackOption" \
    )

    io_query_choice "" choices[@]

    echo

    case "$IOQueryChoice" in
      "$FLUXIONHashVerificationMethodPyritOption")
        local -r verifier="pyrit" ;;

      "$FLUXIONHashVerificationMethodAircrackOption")
        local -r verifier="aircrack-ng" ;;

      "$FLUXIONHashVerificationMethodCowpattyOption")
        local -r verifier="cowpatty" ;;

      "$FLUXIONGeneralBackOption")
        return -1 ;;
    esac
  fi

  hash_check_handshake \
    "$verifier" \
    "$hashPath" \
    "$hashESSID" \
    "$hashBSSID"

  local -r hashResult=$?

  # A value other than 0 means there's an issue with the hash.
  if [ $hashResult -ne 0 ]; then
    echo -e "$FLUXIONVLine $FLUXIONHashInvalidError"
  else
    echo -e "$FLUXIONVLine $FLUXIONHashValidNotice"
  fi

  sleep 3

  if [ $hashResult -ne 0 ]; then return 1; fi
}

fluxion_hash_unset_path() {
  if [ ! "$FluxionHashPath" ]; then return 1; fi
  FluxionHashPath=""

  # Since we're auto-selecting when on auto, trigger undo-chain.
  if [ "$FLUXIONAuto" ]; then return 2; fi
}

# Parameters: <hash path> <bssid> <essid> [channel [encryption [maker]]]
fluxion_hash_set_path() {
  if [ "$FluxionHashPath" ]; then return 0; fi

  fluxion_hash_unset_path

  local -r hashPath=$1

  # If we've got a default path, check if a hash exists.
  # If one exists, ask users if they'd like to use it.
  if [ "$hashPath" -a -f "$hashPath" -a -s "$hashPath" ]; then
    if [ "$FLUXIONAuto" ]; then
      FluxionHashPath=$hashPath
      return
    else
      local choices=( \
        "$FLUXIONUseFoundHashOption" \
        "$FLUXIONSpecifyHashPathOption" \
        "$FLUXIONHashSourceRescanOption" \
        "$FLUXIONGeneralBackOption" \
      )

      fluxion_header

      echo -e "$FLUXIONVLine $FLUXIONFoundHashNotice"
      echo -e "$FLUXIONVLine $FLUXIONUseFoundHashQuery"
      echo

      io_query_choice "" choices[@]

      echo

      case "$IOQueryChoice" in
        "$FLUXIONUseFoundHashOption")
          FluxionHashPath=$hashPath
          return ;;

        "$FLUXIONHashSourceRescanOption")
          fluxion_hash_set_path "$@"
          return $? ;;

        "$FLUXIONGeneralBackOption")
          return -1 ;;
      esac
    fi
  fi

  while [ ! "$FluxionHashPath" ]; do
    fluxion_header

    echo
    echo -e "$FLUXIONVLine $FLUXIONPathToHandshakeFileQuery"
    echo -e "$FLUXIONVLine $FLUXIONPathToHandshakeFileReturnTip"
    echo
    echo -ne "$FLUXIONAbsolutePathInfo: "
    read FluxionHashPath

    # Back-track when the user leaves the hash path blank.
    # Notice: Path is cleared if we return, no need to unset.
    if [ ! "$FluxionHashPath" ]; then return 1; fi

    # Make sure the path points to a valid generic file.
    if [ ! -f "$FluxionHashPath" -o ! -s "$FluxionHashPath" ]; then
      echo -e "$FLUXIONVLine $FLUXIONEmptyOrNonExistentHashError"
      sleep 5
      fluxion_hash_unset_path
    fi
  done
}

# Paramters: <defaultHashPath> <bssid> <essid>
fluxion_hash_get_path() {
  # Assure we've got the bssid and the essid passed in.
  if [ ${#@} -lt 2 ]; then return 1; fi

  while true; do
    fluxion_hash_unset_path
    if ! fluxion_hash_set_path "$@"; then
      return -1 # WARNING: The recent error code is NOT contained in $? here!
    fi

    if fluxion_hash_verify "$FluxionHashPath" "$2" "$3"; then
      break;
    fi
  done

  # At this point FluxionHashPath will be set and ready.
}


# ================== < Attack Subroutines > ================== #
fluxion_unset_attack() {
  local -r attackWasSet=${FluxionAttack:+1}
  FluxionAttack=""
  if [ ! "$attackWasSet" ]; then return 1; fi
}

fluxion_set_attack() {
  if [ "$FluxionAttack" ]; then return 0; fi

  fluxion_unset_attack

  fluxion_header

  echo -e "$FLUXIONVLine $FLUXIONAttackQuery"
  echo

  fluxion_target_show

  local attacks
  readarray -t attacks < <(ls -1 "$FLUXIONPath/attacks")

  local descriptions
  readarray -t descriptions < <(
    head -n 3 "$FLUXIONPath/attacks/"*"/language/$FluxionLanguage.sh" | \
    grep -E "^# description: " | sed -E 's/# \w+: //'
  )

  local identifiers=()

  local attack
  for attack in "${attacks[@]}"; do
    local identifier=$(
      head -n 3 "$FLUXIONPath/attacks/$attack/language/$FluxionLanguage.sh" | \
      grep -E "^# identifier: " | sed -E 's/# \w+: //'
    )
    if [ "$identifier" ]; then
      identifiers+=("$identifier")
    else
      identifiers+=("$attack")
    fi
  done

  attacks+=("$FLUXIONGeneralBackOption")
  identifiers+=("$FLUXIONGeneralBackOption")
  descriptions+=("")

  io_query_format_fields "" \
    "\t$CRed[$CSYel%d$CClr$CRed]$CClr%0.0s $CCyn%b$CClr %b\n" \
    attacks[@] identifiers[@] descriptions[@]

  echo

  if [ "${IOQueryFormatFields[1]}" = "$FLUXIONGeneralBackOption" ]; then
    return -1
  fi

  if [ "${IOQueryFormatFields[1]}" = "$FLUXIONAttackRestartOption" ]; then
    return 2
  fi


  FluxionAttack=${IOQueryFormatFields[0]}
}

fluxion_unprep_attack() {
  if type -t unprep_attack &> /dev/null; then
    unprep_attack
  fi

  IOUtilsHeader="fluxion_header"

  # Remove any lingering targetting subroutines loaded.
  unset attack_targetting_interfaces
  unset attack_tracking_interfaces

  # Remove any lingering restoration subroutines loaded.
  unset load_attack
  unset save_attack

  FluxionTargetTrackerInterface=""

  return 1 # Trigger another undo since prep isn't significant.
}

fluxion_prep_attack() {
  local -r path="$FLUXIONPath/attacks/$FluxionAttack"

  if [ ! -x "$path/attack.sh" ]; then return 1; fi
  if [ ! -x "$path/language/$FluxionLanguage.sh" ]; then return 2; fi

  # Load attack parameters if any exist.
  if [ "$AttackCLIArguments" ]; then
    eval set -- "$AttackCLIArguments"
    # Remove them after loading them once.
    unset AttackCLIArguments
  fi

  # Load attack and its corresponding language file.
  # Load english by default to overwrite globals that ARE defined.
  source "$path/language/en.sh"
  if [ "$FluxionLanguage" != "en" ]; then
    source "$path/language/$FluxionLanguage.sh"
  fi
  source "$path/attack.sh"

  # Check if attack is targetted & set the attack target if so.
  if type -t attack_targetting_interfaces &> /dev/null; then
    if ! fluxion_target_set; then return 3; fi
  fi

  # Check if attack provides tracking interfaces, get & set one.
  # TODO: Uncomment the lines below after implementation.
  if type -t attack_tracking_interfaces &> /dev/null; then
    if ! fluxion_target_set_tracker; then return 4; fi
  fi

  # If attack is capable of restoration, check for configuration.
  if type -t load_attack &> /dev/null; then
    # If configuration file available, check if user wants to restore.
    if [ -f "$path/attack.conf" ]; then
      local choices=( \
        "$FLUXIONAttackRestoreOption" \
        "$FLUXIONAttackResetOption" \
      )

      io_query_choice "$FLUXIONAttackResumeQuery" choices[@]

      if [ "$IOQueryChoice" = "$FLUXIONAttackRestoreOption" ]; then
        load_attack "$path/attack.conf"
      fi
    fi
  fi

  if ! prep_attack; then return 5; fi

  # Save the attack for user's convenience if possible.
  if type -t save_attack &> /dev/null; then
    save_attack "$path/attack.conf"
  fi
}

fluxion_run_attack() {
  start_attack
  fluxion_target_tracker_start

  local choices=( \
    "$FLUXIONSelectAnotherAttackOption" \
    "$FLUXIONGeneralExitOption" \
  )

  io_query_choice \
    "$(io_dynamic_output $FLUXIONAttackInProgressNotice)" choices[@]

  echo

  # IOQueryChoice is a global, meaning, its value is volatile.
  # We need to make sure to save the choice before it changes.
  local choice="$IOQueryChoice"

  fluxion_target_tracker_stop


  # could execute twice
  # but mostly doesn't matter
  if [ ! -x "$(command -v systemctl)" ]; then
    if [ "$(systemctl list-units | grep systemd-resolved)" != "" ];then
        systemctl restart systemd-resolved.service
    fi
  fi

  if [ -x "$(command -v service)" ];then
    if service --status-all | grep -Fq 'systemd-resolved'; then
      sudo service systemd-resolved.service restart
    fi
  fi

  stop_attack

  if [ "$choice" = "$FLUXIONGeneralExitOption" ]; then
    fluxion_handle_exit
  fi

  fluxion_unprep_attack
  fluxion_unset_attack
}

# ============================================================ #
# ================= < Argument Executables > ================= #
# ============================================================ #
eval set -- "$FLUXIONCLIArguments" # Set environment parameters.
while [ "$1" != "" -a "$1" != "--" ]; do
  case "$1" in
    -t|--target) echo "Not yet implemented!"; sleep 3; fluxion_shutdown;;
  esac
  shift # Shift new parameters
done

# ============================================================ #
# ===================== < FLUXION Loop > ===================== #
# ============================================================ #
fluxion_main() {
  fluxion_startup

  fluxion_set_resolution

  # Removed read-only due to local constant shadowing bug.
  # I've reported the bug, we can add it when fixed.
  local sequence=(
    "set_language"
    "set_attack"
    "prep_attack"
    "run_attack"
  )

  while true; do # Fluxion's runtime-loop.
    fluxion_do_sequence fluxion sequence[@]
  done

  fluxion_shutdown
}

fluxion_main # Start Fluxion

# FLUXSCRIPT END
