#!/bin/bash

## Copyright (C) 2012 - 2023 ENCRYPTED SUPPORT LP <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

#set -x
set -e
set -o pipefail
set -o errtrace
#trap 'sleep 1' DEBUG

scriptname="$(basename "$BASH_SOURCE")"

error_handler() {
   local last_exit_code="$?"
   if [ ! "$1" = "" ]; then
      error_text="$1"
   else
      error_text="$BASH_COMMAND"
   fi

   local msg="<p>
<br></br>$scriptname script bug.
<br></br>
<br></br>No panic. Nothing is broken. Just some rare condition has been hit.
<br></br>Try again later. There is likely a solution for this problem.
<br></br>Please see News, Blog and User Help Forum.
<br></br>Please report this bug!
<br></br>
<br></br>msgdispatcher_identifier: '<code>$msgdispatcher_identifier</code>'
<br></br>msgdispatcher_appendix: '<code>$msgdispatcher_appendix</code>'
<br></br>
<br></br>error_text: '<code>$error_text</code>'
<br></br>last_exit_code: '<code>$last_exit_code</code>'
</p>"

   ## Popup window with the message above.
   ## /usr/share/icons/icon-pack-dist/whonix.ico might not exist, but
   ## fortunately msgdispatcher_dispatch_x works anyway.
   /usr/libexec/msgcollector/msgdispatcher_dispatch_x "warning" "$scriptname" "$msg" "0" "/usr/share/icons/icon-pack-dist/whonix.ico" &

   local stripped_msg
   stripped_msg="$(/usr/libexec/msgcollector/striphtml "$msg")"
   if [ "$stripped_msg" = "" ]; then
      ## In case striphtml failed or is not available.
      echo "$msg" >&2
   else
      echo "$stripped_msg" >&2
   fi
   if [ ! -d ~/".msgcollector" ]; then
      mkdir --parents ~/".msgcollector"
   fi
   echo "$scriptname: BASH_COMMAND: $BASH_COMMAND | exit_code: $exit_code" | tee -a ~/".msgcollector/msgdispatcher-error.log" >/dev/null

   true
}

trap "error_handler" ERR

ex_funct() {
   true "$BASH_SOURCE: $FUNCNAME: INFO: Signal $SIGNAL_TYPE received. Cleaning up..."

   if [ ! "$inotifywait_subshell_pid" = "" ]; then
      kill -s sigterm "$inotifywait_subshell_pid" || true
   fi

   if [ -f "$inotifywait_subshell_fifo" ]; then
      safe-rm --force "$inotifywait_subshell_fifo"
   fi

   true "$FUNCNAME: Signal $SIGNAL_TYPE received. Exiting."

   exit "$EXIT_CODE"
}

trap_sighup() {
   set -x
   true
   exit 0
}

trap "trap_sighup" SIGHUP

trap_sigterm() {
   SIGNAL_TYPE="SIGTERM"
   EXIT_CODE="143"
   ex_funct
}

trap "trap_sigterm" SIGTERM

trap_sigint() {
   SIGNAL_TYPE="SIGINT"
   EXIT_CODE="130"
   ex_funct
}

trap "trap_sigint" SIGINT ## ctrl + c

parse_cmd_options() {
   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/035

   while true; do
       case $1 in
           --verbose)
               set -x
               verbose="1"
               shift
               ;;
           --)
               shift
               break
               ;;
           -*)
               echo "$scriptname unknown option: $1" >&2
               exit 1
               ;;
           *)
               break
               ;;
       esac
   done
}

preparation() {
   ## Sanity test.
   command -v flock >/dev/null 2>/dev/null

   true "XDG_SESSION_TYPE: $XDG_SESSION_TYPE"
   true "DISPLAY: $DISPLAY"
   if [ "$XDG_SESSION_TYPE" = "x11" ]; then
      gui=1
      cli=0
   elif [ "$XDG_SESSION_TYPE" = "wayland" ]; then
      gui=1
      cli=0
   elif [ "$XDG_CURRENT_DESKTOP" = "X-QUBES" ]; then
      gui=1
      cli=0
   elif [ ! "$DISPLAY" = "" ]; then
      gui=1
      cli=0
   elif [ "$XDG_SESSION_TYPE" = "tty" ]; then
      cli=1
      ## Do this only in /dev/tty1.
      if [ ! "$(tty)" = "/dev/tty1" ]; then
         echo "$0: INFO: Skip, because not running in /dev/tty1."
         exit 0
      fi
   else
      echo "$0 INFO: XDG_SESSION_TYPE is neither x11 nor wayland nor tty, DISPLAY is not set either, exiting."
      exit 0
   fi

   if [ "$cli" = "1" ]; then
      inotifywait_subshell_fifo="${msgcollector_run_dir}/msgdispatcher_cli_subshell_fifo"
      inotifywait_success_file="${msgcollector_run_dir}/msgdispatcher_cli_inotifywait_success"
   elif [ "$gui" = "1" ]; then
      inotifywait_subshell_fifo="${msgcollector_run_dir}/msgdispatcher_x_subshell_fifo"
      inotifywait_success_file="${msgcollector_run_dir}/msgdispatcher_x_inotifywait_success"
   else
      echo "$$" > "${msgcollector_run_dir}/msgdispatcher_piderror"
      exit 3
   fi

   ## Care for race condition.
   #if [ -f "$inotifywait_success_file" ]; then
      #safe-rm --force "$inotifywait_success_file"
   #fi

   inotifywait_folder="${msgcollector_run_dir}"
   test -d "$inotifywait_folder"
}

## {{ Small wrapper to use either kdialog, notify-send or nothing.
passive_popup_tool() {
   local time title text
   time="$1"
   title="$2"
   text="$3"

   ## Fallback.
   ## notify-send does not work if $title is unset.
   if [ "$title" = "" ]; then
      title="$msgdispatcher_identifier"
   fi

   ## notify-send timeout in milliseconds.

   if command -v "qubesdb-read" >/dev/null 2>&1 ; then
      if command -v "notify-send" >/dev/null 2>&1 ; then
         notify-send --expire-time "${time}000" "$title" "$text"
         return 0
      fi
   fi

   ## check if kdialog, notify-send or no passive popup tool is installed
   ## - that is not the case for CLI Custom-Workstation users
   ## - that may not be the case for Gnome users
   if command -v "kdialog" >/dev/null 2>&1 ; then
      kdialog --title "$title" --passivepopup "$text" "$time"
   elif command -v "notify-send" >/dev/null 2>&1 ; then
      notify-send --expire-time "${time}000" "$title" "$text"
   fi
}
## }}

dispatch_cli() {
   local msg
   msg="$1"

   if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_parenttty" ]; then
      local parenttty
      parenttty="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_parenttty")"
      if [ "$parenttty" = "/dev/tty1" ]; then
         ## When for example systemcheck was run in tty1, then messages were
         ## already echoed by msgcollector. No need to dispatch them again.
         true "Skipping, because parenttty is /dev/tty1."
         return 0
      fi
   fi

   echo "$msg"
}

dispatch_x_active() {
   if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_lefttop" ]; then
      local lefttop
      lefttop="1"
   fi

   local icon
   if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_icon" ]; then
      icon="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_icon")"
   else
      ## Fallback.
      if [ -f "/usr/share/icons/gnome/24x24/status/info.png" ]; then
         icon="/usr/share/icons/gnome/24x24/status/info.png"
      else
         icon=""
      fi
   fi

   ## Fallback.
   if [ "$type" = "" ]; then
      type="info"
   fi

   if [ "$verbose" = "1" ]; then
      /usr/libexec/msgcollector/msgdispatcher_dispatch_x "$type" "$title" "$msg" "$lefttop" "$icon"
   else
      ## Launching into background, so it doesn't block msgdispatcher until
      ## msgdispatcher_dispatch_x exits.
      /usr/libexec/msgcollector/msgdispatcher_dispatch_x "$type" "$title" "$msg" "$lefttop" "$icon" &
   fi
}

dispatch_x_passive() {
   passive_popup_tool "20" "$title" "$msg"
}

inotifywait_setup() {
   ## Prevent race condition while inotifywait might not be started yet.
   ## A subshell is used to manage the lifecycle of inotifywait and ensure it's properly monitored.
   ## The use of a success file is crucial here to determine if inotifywait started correctly.

   if [ ! "$inotifywait_subshell_pid" = "" ]; then
      kill -s sigterm "$inotifywait_subshell_pid" || true
   fi

   safe-rm --force "$inotifywait_subshell_fifo"
   mkfifo "$inotifywait_subshell_fifo"

   {
      inotifywait_subshell_error_handler() {
         local exit_code="$?"
         if [ ! "$1" = "" ]; then
            error_text="exit_code: $exit_code | text: $1"
         else
            error_text="exit_code: $exit_code | BASH_COMMAND: $BASH_COMMAND"
         fi
         safe-rm --force "$inotifywait_success_file"
         error_handler "$error_text"
         exit 1
      }

      trap "inotifywait_subshell_error_handler" ERR

      inotifywait_subshell_trap_sigterm() {
         trap "error_handler" ERR
         true "$BASH_SOURCE: $FUNCNAME: INFO: signal SIGTERM/SIGINT received. Cleaning up..."
         if [ ! "$inotifywait_pid" = "" ]; then
            kill -s sigterm "$inotifywait_pid"
         fi
         safe-rm --force "$inotifywait_success_file"
         true "$BASH_SOURCE: $FUNCNAME: INFO: signal SIGTERM/SIGINT received. Exiting."
         exit 143
      }

      trap "inotifywait_subshell_trap_sigterm" SIGTERM SIGINT

      ## Start inotifywait in a subshell to continuously monitor the directory.
      ## The subshell approach is used so that the script can both start inotifywait and then verify its setup status.
      ## If the inotifywait process fails to set up, the success file won't be created.
      inotifywait --quiet --recursive --monitor --event close_write --format "%w%f" "$inotifywait_folder" &
      inotifywait_pid="$!"
      touch "$inotifywait_success_file"
      inotifywait_wait_exit_code="0"
      true "INFO: Wait for the inotifywait pid..."
      ## If it fails, handle the error gracefully.
      wait "$inotifywait_pid" || { inotifywait_wait_exit_code="$?" ; true; };
      ## Ideally, should never reach beyond this point.

      ## This does not result in the script exiting.
      ## This is because below inotifywait_subshell_pid does not wait to check the exit code of the subshell.
      inotifywait_subshell_error_handler "Failed to set up inotifywait! inotifywait_folder: $inotifywait_folder | inotifywait_wait_exit_code: $inotifywait_wait_exit_code"
      exit "$inotifywait_wait_exit_code"
   } > "$inotifywait_subshell_fifo" &

   ## Does not work:
   #} | sponge -- "$inotifywait_subshell_fifo" &

   inotifywait_subshell_pid="$!"
   true "$scriptname $FUNCNAME (pid: $$): Started subshell for inotify with pid: $inotifywait_subshell_pid"
}

parse_existing_files() {
   ## Sleep to allow inotifywait to set up before processing any existing files.
   ## This prevents any race conditions where existing files might not be processed due to a delay in inotifywait setup.
   sleep "2" &
   true "INFO: wait for parse_existing_files sleep 2 pid"
   wait "$!"

   for folder_name in "$inotifywait_folder/"*; do
      for file_name in "$folder_name/"*; do
         msgdispatcher_handler
      done
   done
}

msgdispatcher_handler() {
   true "PROCESSING file_name: $file_name"
   file_extension="${file_name##*_}"
   if [ ! "$file_extension" = "done" ]; then
      return 0
   fi

   ## Remove "_done".
   temp_item="${file_name%%_*}"
   ## Remove "${msgcollector_run_dir}/".
   msgdispatcher_identifier="${temp_item##*/}"

   if [ "$gui" = "1" ]; then
      if [ "$file_name" = "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagex_done" ]; then
         if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagex" ]; then
            true "INFO: messagex file exists."
            msg="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagex")"
            title="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_titlex")"
            type="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_typex")"
            dispatch_x_active "$type" "$msg"
            msgdispatcher_delete_wrapper "messagex_done"
            msgdispatcher_delete_wrapper "titlex"
            msgdispatcher_delete_wrapper "messagex"
            msgdispatcher_delete_wrapper "lefttop"
            msgdispatcher_delete_wrapper "typex"
         else
            true "INFO: messagex file does NOT exist."
            ## Not using rm outside the if, to prevent race conditions.
            ## Not always using rm, without if to prevent forking.
            msgdispatcher_delete_wrapper "messagex_done"
         fi
         return 0
      fi
      if [ "$file_name" = "${msgcollector_run_dir}/${msgdispatcher_identifier}_passivepopupqueuex_done" ]; then
         if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_passivepopupqueuex" ]; then
            msg="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_passivepopupqueuex")"
            title="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_passivepopupqueuextitle")"
            ## TODO: do not use "_typex" to avoid conflicts with "messagex_done".
            #type="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_typex")"
            type="info"
            if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_forceactive" ]; then
               ## TODO: --forceactive not yet implemented.
               #dispatch_x_active "$type" "$msg"
               dispatch_x_passive "$type" "$title" "$msg"
            else
               dispatch_x_passive "$type" "$title" "$msg"
            fi
            msgdispatcher_delete_wrapper "forceactive"
            msgdispatcher_delete_wrapper "passivepopupqueuex_done"
            msgdispatcher_delete_wrapper "passivepopupqueuex"
            msgdispatcher_delete_wrapper "passivepopupqueuextitle"
         else
            msgdispatcher_delete_wrapper "passivepopupqueuex_done"
         fi
         return 0
      fi

#       last_two="${file_name#*_*_}"
#       if [ "$last_two" = "progressbarx_done" ]; then
#          first_two="${file_name%_*_*}"
#          ## first_two example:
#          ## /run/msgcollector/systemcheck/systemcheck_2b3916d6-3b3f-4490-bc85-b97da494a55d
#          progressbaridx=${first_two#*_}
#          ## progressbaridx example:
#          ## 2b3916d6-3b3f-4490-bc85-b97da494a55d
#          if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbarx" ]; then
#             if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbarx_animate" ]; then
#                msgdispatcher_delete_wrapper "${progressbaridx}_progressbarx_animate"
#                animate="--animate"
#             else
#                unset animate
#             fi
#             if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbartitlex" ]; then
#                local progressbartitlex
#                progressbartitlex="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbartitlex")"
#             fi
#             if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbarx" ]; then
#                progressbarx="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_${progressbaridx}_progressbarx")"
#             fi
#
#             unset animate
#             msgdispatcher_delete_wrapper "${progressbaridx}_progressbarx_done"
#             msgdispatcher_delete_wrapper "${progressbaridx}_progressbartitlex"
#             msgdispatcher_delete_wrapper "${progressbaridx}_progressbarx"
#          else
#             msgdispatcher_delete_wrapper "${progressbaridx}_progressbarx_done"
#          fi
#          return 0
#       fi

   elif [ "$cli" = "1" ]; then
      if [ "$file_name" = "${msgcollector_run_dir}/${msgdispatcher_identifier}_waitmessagecli_done" ]; then
         if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_waitmessagecli" ]; then
            true "INFO: waitmessagecli file exists."
            msg="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_waitmessagecli")"
            type="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_typecli")"
            dispatch_cli "$msg"
            msgdispatcher_delete_wrapper "waitmessagecli_done"
            msgdispatcher_delete_wrapper "waitmessagecli"
         else
            true "INFO: waitmessagecli file does NOT exist."
            msgdispatcher_delete_wrapper "waitmessagecli_done"
         fi
         return 0
      fi
      if [ "$file_name" = "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagecli_done" ]; then
         if [ -f "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagecli" ]; then
            msg="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_messagecli")"
            type="$(cat "${msgcollector_run_dir}/${msgdispatcher_identifier}_typecli")"
            dispatch_cli "$msg"
            msgdispatcher_delete_wrapper "messagecli_done"
            msgdispatcher_delete_wrapper "messagecli"
         else
            msgdispatcher_delete_wrapper "messagecli_done"
         fi
         return 0
      fi
   else
      exit 1
   fi
}

check() {
   if ! [[ "$2" =~ ^[0-9a-zA-Z_-]+$ ]]; then
      echo "$0:
\$1 (variable name): '$1'
\$2 (variable content): '$2'
Invalid character!" >&2
      exit 200
   fi
   if [ "$2" = "" ]; then
      echo "$0:
\$1 (variable name): '$1'
\$2 (variable content): empty!" >&2
      exit 201
   fi
}

msgdispatcher_delete_wrapper() {
   msgdispatcher_appendix="$1"

   check "msgdispatcher_identifier" "${msgdispatcher_identifier}"
   check "msgdispatcher_appendix" "${msgdispatcher_appendix}"

   local file_name
   file_name="${msgcollector_run_dir}/${msgdispatcher_identifier}_${msgdispatcher_appendix}"

   if test -f "$file_name" ; then
      safe-rm -f "$file_name"
   else
      true "INFO: file_name $file_name does not exist."
   fi

   ## In case above errors, do not additionally error out from this function.
   true
}

inotifywait_loop() {
   true "$scriptname $FUNCNAME (pid: $$): Starting loop."

   local temp_item last_two first_two progressbaridx

   while read -r file_name; do
      msgdispatcher_handler
   done < "$inotifywait_subshell_fifo"
}

msgdispatcher_loop() {
   while true; do
      preparation
      fallbacks ## provided by /usr/libexec/msgcollector/msgwmctrl
      inotifywait_setup
      parse_existing_files
      inotifywait_loop
      sleep 10
      true "INFO: wait for main loop sleep 10 pid"
      wait "$!"
   done
}

source /usr/libexec/msgcollector/msgcollector_shared
## sets: ${msgcollector_run_dir}
folder_init

source /usr/libexec/msgcollector/msgwmctrl

parse_cmd_options "$@"
msgdispatcher_loop
