#!/bin/bash

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

set -e
set -o pipefail
#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

   output_msgdispatcher_sudo_wrapper="$(/usr/libexec/msgcollector/br_add "$output_msgdispatcher_sudo_wrapper")" || true

   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 Whonix News, Whonix Blog and Whonix User Help Forum.
<br></br>Please report this bug!
<br></br>
<br></br>who_ami: '<code>$who_ami'</code>
<br></br>msgdisptacher_username: '<code>$msgdisptacher_username</code>'
<br></br>msgdispatcher_identifier: '<code>$msgdispatcher_identifier</code>'
<br></br>msgdispatcher_appendix: '<code>$msgdispatcher_appendix</code>'
<br></br>delete_wrapper: '<code>$delete_wrapper'</code>
<br></br>msgdispatcher_sudo_wrapper_command: '<code>$msgdispatcher_sudo_wrapper_command</code>'
<br></br>output_msgdispatcher_sudo_wrapper: '<code>$output_msgdispatcher_sudo_wrapper</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 "$pid_file" ]; then
      rm --force "$pid_file"
   fi
   if [ -f "$inotifywait_subshell_fifo" ]; then
      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() {
   trap "error_handler" ERR

   SIGNAL_TYPE="SIGTERM"
   EXIT_CODE="143"
   ex_funct
}

trap "trap_sigterm" SIGTERM

trap_sigint() {
   trap "error_handler" ERR

   SIGNAL_TYPE="SIGINT"
   EXIT_CODE="130"
   ex_funct
}

trap "trap_sigint" SIGINT ## ctrl + c

parse_cmd_options() {
   trap "error_handler" ERR

   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/035

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

   if [ "$cli" = "1" ]; then
      return 0
   fi
   if [ "$x" = "1" ]; then
      return 0
   fi
   echo "$0 ERROR: needs to be started with either --x or --cli." >&2
}

preparation() {
   trap "error_handler" ERR

   ## Sanity test.
   command -v flock >/dev/null 2>/dev/null

   who_ami="$(whoami)"

   umask ug=rw,o=r

   if [ ! -d "/run/msgcollector/$who_ami" ]; then
      mkdir --parents --mode ugo+rwx "/run/msgcollector/$who_ami"
   fi

   if [ "$cli" = "1" ]; then
      pid_file="/run/msgcollector/$who_ami/msgdispatcher_pidcli"
      lock_file="/run/msgcollector/$who_ami/msgdispatcher_cli_lock"
      inotifywait_subshell_fifo="/run/msgcollector/$who_ami/msgdispatcher_cli_subshell_fifo"
      inotifywait_success_file="/run/msgcollector/$who_ami/msgdispatcher_cli_inotifywait_success"
   elif [ "$x" = "1" ]; then
      pid_file="/run/msgcollector/$who_ami/msgdispatcher_pidx"
      lock_file="/run/msgcollector/$who_ami/msgdispatcher_x_lock"
      inotifywait_subshell_fifo="/run/msgcollector/$who_ami/msgdispatcher_x_subshell_fifo"
      inotifywait_success_file="/run/msgcollector/$who_ami/msgdispatcher_x_inotifywait_success"
   else
      echo "$$" > "/run/msgcollector/$who_ami/msgdispatcher_piderror"
      exit 3
   fi

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

   inotifywait_folder="/run/msgcollector"
   delete_wrapper="/usr/libexec/msgcollector/msgdispatcher_delete_wrapper"
}

write_own_pid() {
   trap "error_handler" ERR

   local subshell_pid wait_exit_code msgdispatcher_pid
   msgdispatcher_pid="$$"

   (
      trap "error_handler" ERR
      flock_exit_code="0"
      flock --nonblock --exclusive 200 || { flock_exit_code="$?" ; true; };
      true "$scriptname $FUNCNAME (subshell) (pid: $$): lock_file: $lock_file | flock_exit_code: $flock_exit_code"

      if [ -f "$pid_file" ]; then
         true "$scriptname $FUNCNAME (subshell) (pid: $$): pid file $pid_file already exists."
         pid="$(cat "$pid_file")" || true
         true "$scriptname $FUNCNAME (subshell) (pid: $$): pid file $pid_file contains pid: $pid"
         if [ ! "$pid" = "" ]; then
            kill_exit_code="0"
            kill -0 "$pid" || { kill_exit_code="$?" ; true; };
            if [ "$kill_exit_code" = "0" ]; then
               true "$scriptname $FUNCNAME (subshell) (pid: $$): msgdispatcher with pid $pid already running."
               exit 1
            else
              true "$scriptname $FUNCNAME (subshell) (pid: $$): msgdispatcher with pid $pid not running."
            fi
         fi
      fi

      if [ "$flock_exit_code" = "0" ]; then
         true "$scriptname $FUNCNAME (subshell) (pid: $$): writing msgdispatcher_pid $msgdispatcher_pid to pid_file $pid_file."
         echo "$msgdispatcher_pid" > "$pid_file"
      else
         true "$scriptname $FUNCNAME (subshell) (pid: $$): Could not acquire lock."
         exit 2
      fi
   ) 200>"$lock_file" &

   subshell_pid="$!"

   wait_exit_code="0"
   wait "$subshell_pid" || { wait_exit_code="$?" ; true; };

   if [ ! "$wait_exit_code" = "0" ]; then
      true "$scriptname $FUNCNAME (pid: $$): Subshell exit code: $wait_exit_code. Exiting."
      exit 0
   else
      true "$scriptname $FUNCNAME (pid: $$): Acquire lock ok."
   fi
}

## {{ Small wrapper to use either kdialog, notify-send or nothing.
passive_popup_tool() {
   trap "error_handler" ERR

   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() {
   trap "error_handler" ERR

   local msg
   msg="$1"

   if [ -f "/run/msgcollector/$who_ami/${msgdispatcher_identifier}_parenttty" ]; then
      local parenttty
      parenttty="$(cat "/run/msgcollector/$who_ami/${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() {
   trap "error_handler" ERR

   if [ -f "/run/msgcollector/$who_ami/${msgdispatcher_identifier}_lefttop" ]; then
      local lefttop
      lefttop="1"
   fi

   local icon
   if [ -f "/run/msgcollector/$who_ami/${msgdispatcher_identifier}_icon" ]; then
      icon="$(cat "/run/msgcollector/$who_ami/${msgdispatcher_identifier}_icon")"
   else
      ## Fallback.
      if [ -f "/usr/share/icons/icon-pack-dist/whonix.ico" ]; then
         icon="/usr/share/icons/icon-pack-dist/whonix.ico"
      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() {
   trap "error_handler" ERR

   passive_popup_tool "20" "$title" "$msg"
}

inotifywait_setup() {
   trap "error_handler" ERR

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

   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
         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 received. Cleaning up..."
         if [ ! "$inotifywait_pid" = "" ]; then
            kill -s sigterm "$inotifywait_pid"
         fi
         rm --force "$inotifywait_success_file"
         true "$BASH_SOURCE: $FUNCNAME: INFO: signal SIGTERM received. Exiting."
         exit 143
      }

      trap "inotifywait_subshell_trap_sigterm" SIGTERM

      inotifywait --quiet --recursive --monitor --event create --format "%w%f" "$inotifywait_folder" &
      inotifywait_pid="$!"
      touch "$inotifywait_success_file"
      wait_exit_code="0"
      ## Should wait forever.
      wait "$inotifywait_pid" || { wait_exit_code="$?" ; true; };
      inotifywait_subshell_error_handler "Failed to set up inotifywait! inotifywait_folder: $inotifywait_folder | wait_exit_code: $wait_exit_code"
      exit "$wait_exit_code"
   } > "$inotifywait_subshell_fifo" &

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

parse_existing_files() {
   trap "error_handler" ERR

   ## Prevent race condition while inotifywait might not be started yet.
   sleep "2" &
   wait "$!"

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

msgdispatcher_handler() {
   trap "error_handler" ERR

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

   arrIN=(${file_name//// })
   msgdisptacher_username="${arrIN[2]}"

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

   if [ "$x" = "1" ]; then
      if [ "$file_name" = "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_messagex_done" ]; then
         if [ -f "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_messagex" ]; then
            msg="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_messagex")"
            title="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_titlex")"
            type="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_typex")"
            dispatch_x_active "$type" "$msg"
            msgdispatcher_sudo_wrapper "messagex_done"
            msgdispatcher_sudo_wrapper "titlex"
            msgdispatcher_sudo_wrapper "messagex"
            msgdispatcher_sudo_wrapper "lefttop"
            msgdispatcher_sudo_wrapper "typex"
         else
            ## Not using rm outside the if, to prevent race conditions.
            ## Not always using rm, without if to prevent forking.
            msgdispatcher_sudo_wrapper "messagex_done"
         fi
         return 0
      fi
      if [ "$file_name" = "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_passivepopupqueuex_done" ]; then
         if [ -f "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_passivepopupqueuex" ]; then
            msg="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_passivepopupqueuex")"
            title="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_passivepopupqueuextitle")"
            ## TODO: do not use "_typex" to avoid conflicts with "messagex_done".
            #type="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_typex")"
            type="info"
            if [ -f "/run/msgcollector/$msgdisptacher_username/${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_sudo_wrapper "forceactive"
            msgdispatcher_sudo_wrapper "passivepopupqueuex_done"
            msgdispatcher_sudo_wrapper "passivepopupqueuex"
            msgdispatcher_sudo_wrapper "passivepopupqueuextitle"
         else
            msgdispatcher_sudo_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 "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_${progressbaridx}_progressbarx" ]; then
#             if [ -f "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_${progressbaridx}_progressbarx_animate" ]; then
#                msgdispatcher_sudo_wrapper "${progressbaridx}_progressbarx_animate"
#                animate="--animate"
#             else
#                unset animate
#             fi
#             if [ -f "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_${progressbaridx}_progressbartitlex" ]; then
#                local progressbartitlex
#                progressbartitlex="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_${progressbaridx}_progressbartitlex")"
#             fi
#             if [ -f "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_${progressbaridx}_progressbarx" ]; then
#                progressbarx="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_${progressbaridx}_progressbarx")"
#             fi
#
#             unset animate
#             msgdispatcher_sudo_wrapper "${progressbaridx}_progressbarx_done"
#             msgdispatcher_sudo_wrapper "${progressbaridx}_progressbartitlex"
#             msgdispatcher_sudo_wrapper "${progressbaridx}_progressbarx"
#          else
#             msgdispatcher_sudo_wrapper "${progressbaridx}_progressbarx_done"
#          fi
#          return 0
#       fi

   elif [ "$cli" = "1" ]; then
      if [ "$file_name" = "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_waitmessagecli_done" ]; then
         if [ -f "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_waitmessagecli" ]; then
            msg="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_waitmessagecli")"
            type="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_typecli")"
            dispatch_cli "$msg"
            msgdispatcher_sudo_wrapper "waitmessagecli_done"
            msgdispatcher_sudo_wrapper "waitmessagecli"
         else
            msgdispatcher_sudo_wrapper "waitmessagecli_done"
         fi
         return 0
      fi
      if [ "$file_name" = "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_messagecli_done" ]; then
         if [ -f "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_messagecli" ]; then
            msg="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_messagecli")"
            type="$(cat "/run/msgcollector/$msgdisptacher_username/${msgdispatcher_identifier}_typecli")"
            dispatch_cli "$msg"
            msgdispatcher_sudo_wrapper "messagecli_done"
            msgdispatcher_sudo_wrapper "messagecli"
         else
            msgdispatcher_sudo_wrapper "messagecli_done"
         fi
         return 0
      fi
   else
      exit 1
   fi
}

msgdispatcher_sudo_log() {
   trap "error_handler" ERR
   msgdispatcher_sudo_wrapper_command="$@"
   true
}

msgdispatcher_sudo_wrapper() {
   trap "error_handler" ERR

   msgdispatcher_appendix="$1"

   msgdispatcher_sudo_log sudo --non-interactive msgdisptacher_username="$msgdisptacher_username" msgdispatcher_identifier="$msgdispatcher_identifier" msgdispatcher_appendix="$msgdispatcher_appendix" "$delete_wrapper"

   output_msgdispatcher_sudo_wrapper="$(sudo --non-interactive msgdisptacher_username="$msgdisptacher_username" msgdispatcher_identifier="$msgdispatcher_identifier" msgdispatcher_appendix="$msgdispatcher_appendix" "$delete_wrapper" 2>&1)"

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

inotifywait_loop() {
   trap "error_handler" ERR

   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() {
   trap "error_handler" ERR
   while true; do
      maybe_wait
      preparation
      write_own_pid
      fallbacks ## provided by /usr/libexec/msgcollector/msgwmctrl
      inotifywait_setup
      parse_existing_files
      inotifywait_loop
      sleep 10 &
      wait "$!"
   done
}

source /usr/libexec/msgcollector/msgcollector_shared
source /usr/libexec/msgcollector/msgwmctrl

parse_cmd_options "$@"
msgdispatcher_loop
