#! /usr/bin/env bash
if [ -n "$DEBUG" ]; then set -x; fi

show-help() {
  echo "usage: $(basename "$0") [<options>]"
  echo
  echo "Toggle visibility of a window belonging to a specific process:"
  echo "   - If not running the <executable> will be launched."
  echo "   - If process is running <window-name> (or first window found) will be"
  echo "     activated and focused."
  echo "   - If process is running and <window-name> is in focus, minimize it."
  echo
  echo "Options:"
  echo "   -p   (Required) process name given to pgrep to narrow list of"
  echo "        possible target windows."
  echo "   -f   Search full process name instead of just the first word."
  echo "   -e   Executable name or path used to launch process when it is not"
  echo "        running. If not specified, will attempt to use process name"
  echo "        instead."
  echo "   -c   Command used to launch process when it is not running. Can used"
  echo "        instead of -e if the launch command is more complex than a"
  echo "        single executable."
  echo "   -w   Title/name of window which belongs to process to focus on."
  echo "        Allows regular expression matching. If not specified, the first"
  echo "        window found belonging to process will be used."
  echo "   -b   Bring window to current desktop if it is on another one."
  echo "   -a   Assign window to all desktops/workspaces after activating it."
  echo "   -h   Display this help message."
  echo
  echo "Requirements:"
  echo "   - xdotool"
  echo "   - wmctrl"
}

error() {
  echo -e "ERROR: $*" 1>&2
}

error-help() {
  error "$@"
  show-help 1>&2
}

OPT_PROC=""
OPT_FULL=""
OPT_BIN=""
OPT_CMD=""
OPT_WIN=""
OPT_BRING=""
OPT_ALL=""

parse-arguments() {
  while getopts ":p:e:c:w:fbah" opt; do
    case ${opt} in
      p)
        OPT_PROC="$OPTARG"
        ;;
      f)
        OPT_FULL="1"
        ;;
      e)
        OPT_BIN="$OPTARG"
        ;;
      c)
        OPT_CMD="$OPTARG"
        ;;
      w)
        OPT_WIN="$OPTARG"
        ;;
      b)
        OPT_BRING="1"
        ;;
      a)
        OPT_ALL="1"
        ;;
      h)
        show-help
        exit 0
        ;;
      \?)
        error-help "Invalid option: -${OPTARG}\n" 1>&2
        exit 1
        ;;
    esac
  done
  shift $((OPTIND - 1))

  if [ -z "$OPT_PROC" ]; then
    error-help "-p option is required."
    exit 1
  fi

  if [ -z "$OPT_BIN" ]; then
    OPT_BIN="$OPT_PROC"
  fi

  if [ -z "$OPT_CMD" ] && ! command -v "$OPT_BIN" &> /dev/null; then
    error "\"${OPT_BIN}\" does not seem to be a valid executable."
    exit 2
  fi
}

main() {
  local target
  local pid
  local win_id
  local focused_id

  parse-arguments "$@"

  mapfile -t target < <(find-window)
  pid="${target[0]}"
  win_id="${target[1]}"

  if [ -z "$pid" ]; then
    if [ -n "$OPT_CMD" ]; then
      echo "$OPT_PROC not running, launching with: \"$OPT_CMD\""
      exec $OPT_CMD
    else
      echo "$OPT_PROC not running, launching with: \"$OPT_BIN\""
      exec "$OPT_BIN"
    fi
  else
    echo "$OPT_PROC instance found"
    focused_id="$(xdotool getactivewindow)"

    if [ "$win_id" == "$focused_id" ]; then
      echo "Target window is in focus, minimizing"
      xdotool windowminimize "$win_id"
    else
      if [ -n "$OPT_BRING" ]; then
        echo "Bring window to current desktop and activate"
        wmctrl -i -R "$win_id"

        if [ -n "$OPT_ALL" ]; then
          echo "Assign window to all desktops"
          xdotool set_desktop_for_window "$win_id" -1
        fi
      else
        echo "Activating window triggering desktop switch as needed"
        xdotool windowactivate "$win_id"
      fi
    fi
  fi
}

find-window() {
  local pid
  local win_id
  local opts
  local pgrep_opts

  pgrep_opts=()
  opts=(--all --onlyvisible)

  if [ -n "$OPT_WIN" ]; then
    opts+=(--name "$OPT_WIN")
  fi

  if [ -n "$OPT_FULL" ]; then
    pgrep_opts+=(-f)
  fi

  for pid in $(pgrep "${pgrep_opts[@]}" "$OPT_PROC"); do
    win_id="$(xdotool search --pid "$pid" "${opts[@]}")"
    if [ -n "$win_id" ]; then
      echo -e "$pid\n$win_id"
      break
    fi
  done
}

main "$@"
