#!/bin/bash

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

source /usr/libexec/helper-scripts/strings.bsh

msgdispatcher_pid_check() {
   if [ "cli" = "$1" ]; then
      ## msgcollector-cli.service does not exist yet. For now, it's the same systemd unit file.
      if &>/dev/null systemctl --no-pager --no-block --user is-active msgcollector-gui.service ; then
         return 0
      else
         return 1
      fi
   elif [ "gui" = "$1" ]; then
      if &>/dev/null systemctl --no-pager --no-block --user is-active msgcollector-gui.service ; then
         return 0
      else
         ## TODO: Prevents "systemcheck --gui" to write msgcollector gui status files.
         return 1
      fi
   else
      1>&2 printf '%s\n' "msgdispatcher_pid_check: neither cli nor gui!"
      return 1
   fi
}

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

   ## systemcheck sets GUI / CLI variables
   if [ "$GUI" = "true" ]; then
      if msgdispatcher_pid_check "gui" ; then
         msgdispatcher_running_x="true"
      fi
   fi
   if [ "$CLI" = "true" ]; then
      if msgdispatcher_pid_check "cli" ; then
         msgdispatcher_running_cli="true"
      fi
   fi
}

msgdispatcher_init() {
   ## Individual arguments passed to a command in Linux have a maximum size of
   ## 128 KiB, even if ARG_MAX is significantly larger. See
   ## https://mina86.com/2021/the-real-arg-max-part-1/
   ##
   ## To further complicate things, Bash string processing works based on
   ## characters, not bytes, so we have to take into account the possibility
   ## that we've been passed a UTF-8 encoded string that is longer than it
   ## appears. The longest a UTF-8 character in bytes can be is 4 bytes
   ## (according to https://stackoverflow.com/a/9533324), so we divide the 128
   ## KiB max argument size by 4.
   ##
   #arg_max_bytes="$(getconf ARG_MAX)"
   #arg_max_bytes=138300
   arg_max_bytes=32768

   output_tool="/usr/libexec/msgcollector/msgcollector"
   output_general="output_func_general"
   output_x="output_func_x"
   output_cli="output_func_cli"
   msgdispatcher_wait
}

msgdispatcher_wait() {
   while true; do
      if [ "$msgdispatcher_running_x" = "true" ] || [ "$msgdispatcher_running_cli" = "true" ]; then
         break
      fi
      msgdispatcher_run_check
      #loop_protection
      break
   done
}

output_func_general() {
   output_func "$@"
   return "$?"
}

output_func_x() {
   if [ "$msgdispatcher_running_x" = "true" ]; then
      output_func "$@"
      return "$?"
   fi
   true "$0: ${FUNCNAME[0]}: skipping because msgdispatcher_running_x is not set to true."
}

output_func_cli() {
   if [ "$msgdispatcher_running_cli" = "true" ]; then
      output_func "$@"
      return "$?"
   fi
   true "${FUNCNAME[0]}: injecting --onlyecho."
   output_func --onlyecho "$@"
}

output_func_core() {
   if [ "$output_func_verbose" = "true" ]; then
      echo "Running: bash -x $output_tool --identifier $IDENTIFIER ${1+$@}"
      bash -x "$output_tool" --identifier "$IDENTIFIER" "$@"
   else
      "$output_tool" --identifier "$IDENTIFIER" "$@"
   fi
}

output_func() {
   local max_arg_count output_args output_settings message_str msg_idx \
      cut_start_idx max_loop_count loop_idx

   if (( $# < 1 )); then
      1>&2 printf '%s\n' "output_func requires at least one argument!"
      return 1
   fi

   ## If we only got one argument, there's nothing to chunk, pass through the
   ## call to output_func_core immediately.
   if (( $# == 1 )); then
     output_func_core "$@"
     return "$?"
   fi

   max_arg_count=4096
   max_loop_count=100
   output_args=( "$@" )
   output_settings=( "${output_args[@]:0:${#output_args[@]}-1}" )
   message_str="${output_args[${#output_args[@]}-1]}"
   cut_start_idx=0

   if (( ${#output_args[@]} > max_arg_count )); then
      1>&2 printf '%s\n' "output_func settings contains too many arguments!"
      return 1
   fi

   ## Split a string into chunks, using the newline character as the
   ## separator. Create as large of chunks as possible.
   [ -v arg_max_bytes ] || arg_max_bytes=32768
   ## Guard against a malicious arg_max_bytes environment variable if
   ## output_func gets called before msgdispatcher_init.
   is_whole_number "$arg_max_bytes" || arg_max_bytes=32768

   for (( loop_idx = 0; loop_idx < max_loop_count; loop_idx++ )); do
      (( msg_idx = cut_start_idx + ( arg_max_bytes - 1 ) )) || true

      if (( msg_idx >= ( ${#message_str} - 1 ) )); then
         ## The index is pointing at the end of the string. Usually we leave
         ## off the last character, but this time we want to keep it, thus we
         ## aren't removing 1 from ${#message_str} here.
         (( msg_idx = ${#message_str} )) || true

         if [ "${message_str:msg_idx-1:1}" = $'\n' ]; then
           ## OK, but if the last character is a newline we actually do want
           ## to leave that off.
           (( msg_idx-- )) || true
         fi

         if (( msg_idx <= cut_start_idx )); then
            break
         fi

         output_func_core "${output_settings[@]}" \
            "${message_str:cut_start_idx:msg_idx - cut_start_idx}" \
               || return "$?"
         break
      fi

      ## Search backwards for a newline.
      while true; do
         if (( msg_idx <= cut_start_idx )); then
            1>&2 printf '%s\n' 'output_func: Cannot break massive argument into chunks!'
            return 1
         fi

         if [ "${message_str:msg_idx:1}" = $'\n' ]; then
            break
         fi
         (( msg_idx-- )) || true
      done

      output_func_core "${output_settings[@]}" \
         "${message_str:cut_start_idx:msg_idx - cut_start_idx}" \
            || return "$?"

      (( cut_start_idx = msg_idx + 1 )) || true
   done

   if (( loop_idx >= max_loop_count )); then
      1>&2 printf '%s\n' "output_func: Possible infinite loop hit!"
      return 1
   fi
}
