#!/usr/bin/env bash
readonly XP_SHELL="/usr/bin/env bash"

# @Author Yamada, Yasuhiro
# @Filename xpanes

set -u
readonly XP_VERSION="4.1.1"

## trap might be updated in 'xpns_pre_execution' function
trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$; rm -f "${XP_DEFAULT_SOCKET_PATH}"' EXIT

## --------------------------------
# Error constants
## --------------------------------
# Undefined or General errors
readonly XP_EUNDEF=1

# Invalid option/argument
readonly XP_EINVAL=4

# Could not open tty.
readonly XP_ETTY=5

# Invalid layout.
readonly XP_ELAYOUT=6

# Impossible layout: Small pane
readonly XP_ESMLPANE=7

# Log related exit status is 2x.
## Could not create a directory.
readonly XP_ELOGDIR=20

## Could not directory to store logs is not writable.
readonly XP_ELOGWRITE=21

# User's intentional exit is 3x
## User exit the process intentionally by following warning message.
readonly XP_EINTENT=30

## All the panes are closed before processing due to user's options/command.
readonly XP_ENOPANE=31

# Necessary commands are not found
readonly XP_ENOCMD=127

# ===============

# XP_THIS_FILE_NAME is supposed to be "xpanes".
readonly XP_THIS_FILE_NAME="${0##*/}"
readonly XP_THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-${(%):-%N}}")" && pwd)"
readonly XP_ABS_THIS_FILE_NAME="${XP_THIS_DIR}/${XP_THIS_FILE_NAME}"

# Prevent cache directory being created under root / directory in any case.
# This is quite rare case (but it can be happened).
readonly XP_USER_HOME="${HOME:-/tmp}"

# Basically xpanes follows XDG Base Direcotry Specification.
# https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html
XDG_CACHE_HOME="${XDG_CACHE_HOME:-${XP_USER_HOME}/.cache}"
readonly XP_CACHE_HOME="${XDG_CACHE_HOME}/xpanes"

# This is supposed to be xpanes-12345(PID)
readonly XP_SESSION_NAME="${XP_THIS_FILE_NAME}-$$"
# Temporary window name is tmp-12345(PID)
readonly XP_TMP_WIN_NAME="tmp-$$"
readonly XP_EMPTY_STR="EMPTY"

readonly XP_SUPPORT_TMUX_VERSION_LOWER="1.8"

# Check dependencies just in case.
# Even POSIX compliant commands are only used in this program.
# `xargs`, `sleep`, `mkfifo` are omitted because minimum functions can work without them.
readonly XP_DEPENDENCIES="${XP_DEPENDENCIES:-tmux grep sed tr od echo touch printf cat sort pwd cd mkfifo}"

## --------------------------------
# User customizable shell variables
## --------------------------------
TMUX_XPANES_EXEC=${TMUX_XPANES_EXEC:-tmux}
TMUX_XPANES_PANE_BORDER_FORMAT="${TMUX_XPANES_PANE_BORDER_FORMAT:-#[bg=green,fg=black] #T #[default]}"
TMUX_XPANES_PANE_BORDER_STATUS="${TMUX_XPANES_PANE_BORDER_STATUS:-bottom}"
TMUX_XPANES_PANE_DEAD_MESSAGE=${TMUX_XPANES_PANE_DEAD_MESSAGE:-'\033[41m\033[4m\033[30m Pane is dead: Press [Enter] to exit... \033[0m\033[39m\033[49m'}
XP_DEFAULT_TMUX_XPANES_LOG_FORMAT="[:ARG:].log.%Y-%m-%d_%H-%M-%S"
TMUX_XPANES_LOG_FORMAT="${TMUX_XPANES_LOG_FORMAT:-${XP_DEFAULT_TMUX_XPANES_LOG_FORMAT}}"
XP_DEFAULT_TMUX_XPANES_LOG_DIRECTORY="${XP_CACHE_HOME}/logs"
TMUX_XPANES_LOG_DIRECTORY="${TMUX_XPANES_LOG_DIRECTORY:-${XP_DEFAULT_TMUX_XPANES_LOG_DIRECTORY}}"

## --------------------------------
# Initialize Options
## --------------------------------
# options which work individually.
readonly XP_FLAG_OPTIONS="[hVdetxs]"
# options which need arguments.
readonly XP_ARG_OPTIONS="[ISclnCRB]"
readonly XP_DEFAULT_LAYOUT="tiled"
readonly XP_DEFAULT_REPSTR="{}"
readonly XP_DEFAULT_CMD_UTILITY="echo {} "
readonly XP_SSH_CMD_UTILITY="ssh -o StrictHostKeyChecking=no {} "
XP_OPTIONS=()
XP_ARGS=()
XP_STDIN=()
XP_BEGIN_ARGS=()
XP_IS_PIPE_MODE=0
XP_OPT_IS_SYNC=1
XP_OPT_DRY_RUN=0
XP_OPT_ATTACH=1
XP_OPT_LOG_STORE=0
XP_REPSTR=""
XP_DEFAULT_SOCKET_PATH_BASE="${XP_CACHE_HOME}/socket"
XP_DEFAULT_SOCKET_PATH="${XP_DEFAULT_SOCKET_PATH_BASE}.$$"
XP_SOCKET_PATH="${XP_SOCKET_PATH:-${XP_DEFAULT_SOCKET_PATH}}"
XP_NO_OPT=0
XP_OPT_CMD_UTILITY=0
XP_CMD_UTILITY=""
XP_LAYOUT="${XP_DEFAULT_LAYOUT}"
XP_MAX_PANE_ARGS=""
XP_OPT_SET_TITLE=0
XP_OPT_CHANGE_BORDER=0
XP_OPT_EXTRA=0
XP_OPT_SPEEDY=0
XP_OPT_SPEEDY_AWAIT=0
XP_OPT_USE_PRESET_LAYOUT=0
XP_OPT_CUSTOM_SIZE_COLS=
XP_OPT_CUSTOM_SIZE_ROWS=
XP_OPT_BULK_COLS=
XP_WINDOW_WIDTH=
XP_WINDOW_HEIGHT=
XP_COLS=
XP_COLS_OFFSETS=
XP_OPT_DEBUG=0
XP_OPT_IGNORE_SIZE_LIMIT=0

## --------------------------------
# Logger
#   $1 -- Log level (i.e Warning, Error)
#   $2 -- Message
#   i.e
#      xpanes:Error: invalid option.
#
# This log format is created with reference to openssl's one.
#   $ echo | openssl -a
#   openssl:Error: '-a' is an invalid command.
## --------------------------------
xpns_msg() {
  local _loglevel="$1"
  local _msgbody="$2"
  local _msg="${XP_THIS_FILE_NAME}:${_loglevel}: ${_msgbody}"
  printf "%s\\n" "${_msg}" >&2
}

xpns_msg_info() {
  xpns_msg "Info" "$1"
}

xpns_msg_warning() {
  xpns_msg "Warning" "$1"
}

xpns_msg_debug() {
  if [[ $XP_OPT_DEBUG -eq 1 ]];then
    xpns_msg "Debug" "$(date "+[%F_%T]"):${FUNCNAME[1]}:$1"
  fi
}

xpns_msg_error() {
  xpns_msg "Error" "$1"
}

xpns_usage_warn() {
  xpns_usage_short >&2
  echo "Try '${XP_THIS_FILE_NAME} --help' for more information." >&2
}

xpns_usage_short() {
  cat << _EOS_
Usage: ${XP_THIS_FILE_NAME} [OPTIONS] [argument ...]
Usage(Pipe mode): command ... | ${XP_THIS_FILE_NAME} [OPTIONS] [<command> ...]
_EOS_
}

xpns_usage() {
  cat <<USAGE
Usage:
  ${XP_THIS_FILE_NAME} [OPTIONS] [argument ...]

Usage(Pipe mode):
  command ... | ${XP_THIS_FILE_NAME} [OPTIONS] [<command> ...]

OPTIONS:
  -h,--help                    Display this help and exit.
  -V,--version                 Output version information and exit.
  -B <begin-command>           Run <begin-command> before processing <command> in each pane. Multiple options are allowed.
  -c <command>                 Set <command> to be executed in each pane. Default is \`echo {}\`.
  -d,--desync                  Make synchronize-panes option off in new window.
  -e                           Execute given arguments as is. Same as \`-c '{}'\`
  -I <repstr>                  Replacing one or more occurrences of <repstr> in command provided by -c or -B. Default is \`{}\`.
  -C NUM,--cols=NUM            Number of columns of window layout.
  -R NUM,--rows=NUM            Number of rows of window layout.
  -l <layout>                  Set the preset of window layout. Recognized layout arguments are:
                               t    tiled
                               eh   even-horizontal
                               ev   even-vertical
                               mh   main-horizontal
                               mv   main-vertical
  -n <number>                  Set the maximum number of <argument> taken for each pane.
  -s                           Speedy mode: Run command without opening an interactive shell.
  -ss                          Speedy mode AND close a pane automatically at the same time as process exiting.
  -S <socket-path>             Set a full alternative path to the server socket.
  -t                           Display each argument on the each pane's border as their title.
  -x                           Create extra panes in the current active window.
  --log[=<directory>]          Enable logging and store log files to ~/.cache/xpanes/logs or <directory>.
  --log-format=<FORMAT>        Make name of log files follow <FORMAT>. Default is \`${XP_DEFAULT_TMUX_XPANES_LOG_FORMAT}\`.
  --ssh                        Same as \`-t -s -c 'ssh -o StrictHostKeyChecking=no {}'\`.
  --stay                       Do not switch to new window.
  --bulk-cols=NUM1[,NUM2 ...]  Set number of columns on multiple rows (i.e, "2,2,2" represents 2 cols x 3 rows).
  --debug                      Print debug message.

Copyright (c) 2019 Yamada, Yasuhiro
Released under the MIT License.
https://github.com/greymd/tmux-xpanes
USAGE
}

# Show version number
xpns_version() {
  echo "${XP_THIS_FILE_NAME} ${XP_VERSION}"
}

# Get version number for tmux
xpns_get_tmux_version() {
  local _tmux_version=""
  if ! ${TMUX_XPANES_EXEC} -V &> /dev/null; then
    # From tmux 0.9 to 1.3, there is no -V option.
    _tmux_version="tmux 0.9-1.3"
  else
    _tmux_version="$( ${TMUX_XPANES_EXEC} -V )"
  fi
  ( read -r _ _ver; echo "${_ver}" ) <<<"${_tmux_version}"
}

# Check whether the prefered tmux version is greater than host's tmux version.
# $1 ... Prefered version.
# $2 ... Host tmux version(optional).
# In case of tmux version is 1.7, the result will be like this.
# 0 is true, 1 is false.
##  arg  -> result
#   func 1.5  1.7 -> 0
#   func 1.6  1.7 -> 0
#   func 1.7  1.7 -> 0
#   func 1.8  1.7 -> 1
#   func 1.9  1.7 -> 1
#   func 1.9a 1.7 -> 1
#   func 2.0  1.7 -> 1
xpns_tmux_is_greater_equals() {
  local _check_version="$1"
  local _tmux_version="${2:-$(xpns_get_tmux_version)}"
  # Simple numerical comparison does not work because there is the version like "1.9a".
  if [[ "$( (echo "${_tmux_version}"; echo "${_check_version}") | sort -n | head -n 1)" != "${_check_version}" ]];then
    return 1
  else
    return 0
  fi
}

xpns_get_local_tmux_conf() {
  local _conf_name="$1"
  local _session="${2-}"
  {
    if [[ -z "${_session-}" ]];then
      ${TMUX_XPANES_EXEC} show-window-options
    else
      ${TMUX_XPANES_EXEC} -S "${_session}" show-window-options
    fi
  } | grep "^${_conf_name}" \
    | ( read -r _ _v; printf "%s\\n" "${_v}" )
}

xpns_get_global_tmux_conf() {
  local _conf_name="$1"
  local _session="${2-}"
  {
    if [[ -z "${_session-}" ]];then
      ${TMUX_XPANES_EXEC} show-window-options -g
    else
      ${TMUX_XPANES_EXEC} -S "${_session}" show-window-options -g
    fi
  } | grep "^${_conf_name}" \
    | ( read -r _ _v; printf "%s\\n" "${_v}" )
}

# Disable allow-rename because
# window separation does not work correctly
# if "allow-rename" option is on
xpns_suppress_allow_rename () {
  local _default_allow_rename="$1"
  local _session="${2-}"
  if [[ "${_default_allow_rename-}" == "on"  ]]; then
    ## Temporary, disable "allow-rename"
    xpns_msg_debug "'allow-rename' option is 'off' temporarily."
    if [[ -z "${_session-}" ]];then
      ${TMUX_XPANES_EXEC} set-window-option -g allow-rename off
    else
      ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename off
    fi
  fi
}

# Restore default "allow-rename"
# Do not write like 'xpns_restore_allow_rename "some value" "some value" > /dev/null'
# In tmux 1.6, 'tmux set-window-option' might be stopped in case of redirection.
xpns_restore_allow_rename () {
  local _default_allow_rename="$1"
  local _session="${2-}"
  if [[ "${_default_allow_rename-}" == "on"  ]]; then
    xpns_msg_debug "Restore original value of 'allow-rename' option."
    if [[ -z "${_session-}" ]];then
      ${TMUX_XPANES_EXEC} set-window-option -g allow-rename on
    else
      ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename on
    fi
  fi
}

# func "11" "2"
#  => 6
# 11 / 2 = 5.5 => ceiling => 6
xpns_ceiling () {
  local _divide="$1";shift
  local _by="$1"
  printf "%s\\n" $(( ( _divide + _by - 1 ) / _by ))
}

# func "10" "3"
#  => 4 3 3
# Divide 10 into 3 parts as equally as possible.
xpns_divide_equally () {
  local _number="$1";shift
  local _count="$1"
  local _upper _lower _upper_count _lower_count
  _upper="$(xpns_ceiling "$_number" "$_count")"
  _lower=$(( _upper - 1 ))
  _lower_count=$(( _upper * _count - _number ))
  _upper_count=$(( _count - _lower_count ))
  eval "printf '${_upper} %.0s' {1..$_upper_count}"
  (( _lower_count > 0 )) && eval "printf '${_lower} %.0s' {1..$_lower_count}"
}

# echo 3 3 3 3 | func
# => 3 6 9 12
xpns_nums_accumulate_sum () {
  local s=0
  while read -r n; do
    ((s = s + n ))
    printf "%s " "$s"
  done < <( cat | tr ' ' '\n')
}

# func 3 2 2 2
# => 4 4 1
#
# For example, "3 2 2 2" represents following cell positions
#   1  2  3
# 1 [] [] [] => 3 rows
# 2 [] []    => 2 rows
# 3 [] []    => 2 rows
# 4 [] []    => 2 rows
#
# After the transposition, it must be "4 4 1" which represents below
#   1  2  3  4
# 1 [] [] [] [] => 4 rows
# 2 [] [] [] [] => 4 rows
# 3 []          => 1 rows
xpns_nums_transpose () {
  local _colnum="$1"
  local _spaces=
  local _result=
  xpns_msg_debug "column num = $_colnum, input = $*"
  _spaces="$(for i in "$@";do
    printf "%${i}s\\n"
  done)"

  # 'for' statement does not work somehow
  _result="$(while read -r i; do
    ## This part is depending on the following 'cut' behavior
    ## $ echo 1234 | cut -c 5
    ## => result is supposed to be empty
    printf "%s\\n" "$_spaces" | cut -c "$i" | grep -c ' '
  done < <(xpns_seq 1 "${_colnum}") | xargs)"
  xpns_msg_debug "result = $_result"
  printf "%s\\n" "$_result"
}

# Adjust size of columns and rows in accordance with given N
# func <col> <row> <N>
# i.e:
#     func "" "" 20
#       => returns 4 5
#     func "6" 0 20
#       => returns 6 4
xpns_adjust_col_row() {
  local col="${1:-0}" ;shift
  local row="${1:-0}" ;shift
  local N="$1"   ;shift
  local fix_col_flg
  local fix_row_flg
  (( col != 0 )) && fix_col_flg=1 || fix_col_flg=0
  (( row != 0 )) && fix_row_flg=1 || fix_row_flg=0

  # This is just a author (@greymd)'s preference.
  if (( fix_col_flg == 0 )) && (( fix_row_flg == 0 )) && (( N == 2)) ;then
    col=2
    row=1
    printf "%d %d\\n" "${col}" "${row}"
    return
  fi

  # If both valures are provided, col is used.
  if (( fix_col_flg == 1 )) && (( fix_row_flg == 1 ));then
    row=0
    fix_row_flg=0
  fi
  # This algorhythm is almost same as tmux default
  #   https://github.com/tmux/tmux/blob/2.8/layout-set.c#L436
  while (( col * row < N )) ;do
    (( fix_row_flg != 1 )) && (( row = row + 1 ))
    if (( col * row < N ));then
      (( fix_col_flg != 1 )) &&  (( col = col + 1 ))
    fi
  done
  printf "%d %d\\n" "${col}" "${row}"
}

# Make each line unique by adding index number
# echo aaa bbb ccc aaa ccc ccc | xargs -n 1 | xpns_unique_line
#  aaa-1
#  bbb-1
#  ccc-1
#  aaa-2
#  ccc-2
#  ccc-3
#
# Eval is used because associative array is not supported before bash 4.2
xpns_unique_line () {
  local _val_name
  while read -r line; do
    _val_name="__xpns_hash_$(printf "%s" "${line}" | xpns_value2key)"
    # initialize variable
    eval "${_val_name}=\${${_val_name}:-0}"
    # increment variable
    eval "${_val_name}=\$(( ++${_val_name} ))"
    printf "%s\\n" "${line}-$(eval printf "%s" "\$${_val_name}")"
  done
}

#
# Generate log file names from given arguments.
# Usage:
#        echo <arg1> <arg2> ... | xpns_log_filenames <FORMAT>
# Return:
#        File names.
# Example:
#        $ echo aaa bbb ccc aaa ccc ccc | xargs -n 1 | xpns_log_filenames '[:ARG:]_[:PID:]_%Y%m%d.log'
#        aaa-1_1234_20160101.log
#        bbb-1_1234_20160101.log
#        ccc-1_1234_20160101.log
#        aaa-2_1234_20160101.log
#        ccc-2_1234_20160101.log
#        ccc-3_1234_20160101.log
#
xpns_log_filenames () {
  local _arg_fmt="$1"
  local _full_fmt=
  _full_fmt="$(date "+${_arg_fmt}")"
  cat \
    | \
    # 1st argument + '-' + unique number (avoid same argument has same name)
    xpns_unique_line \
    | while read -r _arg
    do
      cat <<<"${_full_fmt}" \
        | sed "s/\\[:ARG:\\]/${_arg}/g" \
        | sed "s/\\[:PID:\\]/$$/g"
    done
}

## --------------------------------
# Normalize directory by making following conversion.
#  * Tilde expansion.
#  * Remove the slash '/' at the end of the dirname.
# Usage:
#        xpns_normalize_directory <direname>
# Return:
#        Normalized <dirname>
## --------------------------------
xpns_normalize_directory() {
  local _dir="$1"
  # Remove end of slash '/'
  _dir="${_dir%/}"
  # tilde expansion
  _dir="${_dir/#~/${HOME}}"
  printf "%s\\n" "${_dir}"
}

## --------------------------------
# Ensure existence of given directory
# Usage:
#        xpns_is_valid_directory <direname>
# Return:
#        Absolute path of the <dirname>
## --------------------------------
xpns_is_valid_directory() {
  local _dir="$1"
  local _checkfile="${XP_THIS_FILE_NAME}.$$"
  # Check directory.
  if [[ ! -d "${_dir}" ]]; then
    # Create directory
    if mkdir "${_dir}"; then
      xpns_msg_info "${_dir} is created."
    else
      xpns_msg_error "Failed to create ${_dir}"
      exit ${XP_ELOGDIR}
    fi
  fi
  # Try to create file.
  #   Not only checking directory permission,
  #   but also i-node and other misc situations.
  if ! touch "${_dir}/${_checkfile}"; then
    xpns_msg_error "${_dir} is not writable."
    rm -f "${_dir}/${_checkfile}"
    exit ${XP_ELOGWRITE}
  fi
  rm -f "${_dir}/${_checkfile}"
}

# Convert array to string which is can be used as command line argument.
# Usage:
#       xpns_arr2args <array object>
# Example:
#       array=(aaa bbb "ccc ddd" eee "f'f")
#       xpns_arr2args "${array[@]}"
#       @returns "'aaa' 'bbb' 'ccc ddd' 'eee' 'f\'f'"
# Result:
xpns_arr2args() {
  local _arg=""
  # If there is no argument, usage will be shown.
  if [[ $# -lt 1 ]]; then
    return 0
  fi
  for i in "$@" ;do
    _arg="${i}"
    # Use 'cat <<<"input"' command instead of 'echo',
    # because such the command recognizes option like '-e'.
    cat <<<"${_arg}" \
      | \
      # Escaping single quotations.
      sed "s/'/'\"'\"'/g" \
      | \
      # Surround argument with single quotations.
      sed "s/^/'/;s/$/' /" \
      | \
      # Remove new lines
      tr -d '\n'
  done
}

# Extract first field to generate window name.
# ex, $2     =  'aaa bbb ccc'
#   return   =  aaa-12345(PID)
xpns_generate_window_name() {
  local _unprintable_str="${1-}"; shift
  # Leave first 200 characters to prevent
  # the name exceed the maximum length of tmux window name (2000 byte).
  printf "%s\\n" "${1:-${_unprintable_str}}" \
    | ( read -r _name _ && printf "%s\\n" "${_name:0:200}-$$" )
}

# Convert string to another string which can be handled as tmux window name.
xpns_value2key() {
  od -v -tx1 -An  | tr -dc 'a-zA-Z0-9' | tr -d '\n'
}

# Restore string encoded by xpns_value2key function.
xpns_key2value() {
  read -r _key
  # shellcheck disable=SC2059
  printf "$(printf "%s" "$_key" | sed 's/../\\x&/g')"
}

# Remove empty lines
# This function behaves like `awk NF`
xpns_rm_empty_line() {
  { cat; printf "\\n";} | while IFS= read -r line;do
    # shellcheck disable=SC2086
    set -- ${line-}
    if [[ $# != 0 ]]; then
      printf "%s\\n" "${line}"
    fi
  done
}

# Extract matched patterns from string
# $ xpns_extract_matched "aaa123bbb" "[0-9]{3}"
# => "123"
xpns_extract_matched() {
  local _args="$1" ;shift
  local _regex="($1)"
  if [[ $_args =~ $_regex ]];then
    printf "%s" "${BASH_REMATCH[0]}"
  fi
}

# Enable logging feature to the all the panes in the target window.
xpns_enable_logging() {
  local _window_name="$1"     ; shift
  local _index_offset="$1"    ; shift
  local _log_dir="$1"         ; shift
  local _log_format="$1"      ; shift
  local _unprintable_str="$1" ; shift
  local _args=("$@")
  local _args_num=$(($# - 1))
  # Generate log files from arguments.
  local _idx=0
  while read -r _logfile ; do
    # Start logging
    xpns_msg_debug "Start logging pipe-pane(cat >> '${_log_dir}/${_logfile}')"
    ${TMUX_XPANES_EXEC} \
      pipe-pane -t "${_window_name}.$(( _idx + _index_offset ))" \
      "cat >> '${_log_dir}/${_logfile}'" # Tilde expansion does not work here.
    _idx=$(( _idx + 1 ))
  done < <(
  for i in $(xpns_seq 0 "${_args_num}")
  do
    # Replace empty string.
    printf "%s\\n" "${_args[i]:-${_unprintable_str}}"
  done | xpns_log_filenames "${_log_format}"
  )
}

## Print "1" on the particular named pipe
xpns_notify() {
  local _wait_id="$1" ; shift
  local _fifo=
  _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}"
  xpns_msg_debug "Notify to $_fifo"
  printf "%s\\n" 1 > "$_fifo" &
}

xpns_notify_logging() {
  local _window_name="$1" ; shift
  local _args_num=$(($# - 1))
  for i in $(xpns_seq 0 "${_args_num}"); do
    xpns_notify "log_${_window_name}-${i}-$$"
  done
}

xpns_notify_sync() {
  local _window_name="$1" ; shift
  local _args_num=$(($# - 1))
  for i in $(xpns_seq 0 "${_args_num}"); do
    xpns_notify "sync_${_window_name}-${i}-$$" &
  done
}

xpns_is_window_alive() {
  local _window_name="$1" ;shift
  local _speedy_await_flag="$1" ;shift
  local _def_allow_rename="$1" ;shift
  if ! ${TMUX_XPANES_EXEC} display-message -t "$_window_name" -p > /dev/null 2>&1 ;then
    xpns_msg_info "All the panes are closed before displaying the result."
    if [[ "${_speedy_await_flag}" -eq 0 ]] ;then
      xpns_msg_info "Use '-s' option instead of '-ss' option to avoid this behavior."
    fi
    xpns_restore_allow_rename "${_def_allow_rename-}"
    exit ${XP_ENOPANE}
  fi
}

xpns_inject_title() {
  local _target_pane="$1" ;shift
  local _message="$1"     ;shift
  local _pane_tty=
  _pane_tty="$( ${TMUX_XPANES_EXEC} display-message -t "${_target_pane}" -p "#{pane_tty}" )"
  printf "\\033]2;%s\\033\\\\" "${_message}" > "${_pane_tty}"
  xpns_msg_debug "target_pane=${_target_pane} pane_title=${_message} pane_tty=${_pane_tty}"
}

xpns_is_pane_title_required() {
  local _title_flag="$1"   ; shift
  local _extra_flag="$1"   ; shift
  local _pane_border_status=
  _pane_border_status=$(xpns_get_local_tmux_conf "pane-border-status")
  if [[ $_title_flag -eq 1 ]]; then
    return 0
  elif [[ ${_extra_flag} -eq 1 ]] && \
       [[ "${_pane_border_status}" != "off" ]] && \
       [[ -n "${_pane_border_status}" ]] ;then
    ## For -x option
    # Even the -t option is not specified, it is required to inject pane title here.
    # Because user expects the title is displayed on the pane if the original window is
    # generated from tmux-xpanes with -t option.
    return 0
  fi
  return 1
}

# Set pane titles for each pane for -t option
xpns_set_titles() {
  local _window_name="$1"  ; shift
  local _index_offset="$1" ; shift
  local _index=0
  local _pane_index=
  for arg in "$@"
  do
    _pane_index=$(( _index + _index_offset ))
    xpns_inject_title "${_window_name}.${_pane_index}" "${arg}"
    _index=$(( _index + 1 ))
  done
}

# Send command to the all the panes in the target window.
xpns_send_commands() {
  local _window_name="$1"  ; shift
  local _index_offset="$1" ; shift
  local _repstr="$1"       ; shift
  local _cmd="$1"          ; shift
  local _index=0
  local _pane_index=
  local _exec_cmd=
  for arg in "$@"
  do
    _exec_cmd="${_cmd//${_repstr}/${arg}}"
    _pane_index=$(( _index + _index_offset ))
    ${TMUX_XPANES_EXEC} send-keys -t "${_window_name}.${_pane_index}" "${_exec_cmd}" C-m
    _index=$(( _index + 1 ))
  done
}

# Separate window vertically, when the number of panes is 1 or 2.
xpns_organize_panes() {
  local _window_name="$1" ; shift
  local _args_num="$1"
  ## ----------------
  # Default behavior
  ## ----------------
  if [[ "${_args_num}" -eq 1 ]]; then
    ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" even-horizontal
  elif [[ "${_args_num}" -gt 1 ]]; then
    ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" tiled
  fi
  ## ----------------
  # Update layout
  ## ----------------
  if [[ "${XP_LAYOUT}" != "${XP_DEFAULT_LAYOUT}" ]]; then
    ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" "${XP_LAYOUT}"
  fi
}

#
# Generate sequential number descending order.
# seq is not used because old version of
# seq does not generate descending oorder.
# $ xpns_seq 3 0
# 3
# 2
# 1
# 0
#
xpns_seq () {
  local _num1="$1"
  local _num2="$2"
  eval "printf \"%d\\n\" {$_num1..$_num2}"
}

xpns_wait_func() {
  local _wait_id="$1"
  local _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}"
  local _arr=("$_fifo")
  local _fifo_arg=
  _fifo_arg=$(xpns_arr2args "${_arr[@]}")
  xpns_msg_debug "mkfifo $_fifo"
  mkfifo "${_fifo}"
  xpns_msg_debug "grep -q 1 ${_fifo_arg}"
  printf "%s\\n" "grep -q 1 ${_fifo_arg}"
}

# Split a new window into multiple panes.
#
xpns_split_window() {
  local _window_name="$1"     ; shift
  local _log_flag="$1"        ; shift
  local _title_flag="$1"      ; shift
  local _speedy_flag="$1"     ; shift
  local _await_flag="$1"      ; shift
  local _pane_base_index="$1" ; shift
  local _repstr="$1"          ; shift
  local _cmd_template="$1"    ; shift
  local _exec_cmd=
  local _sep_count=0
  local args=("$@")
  _last_idx=$(( ${#args[@]} - 1 ))

  for i in $(xpns_seq $_last_idx 0)
  do
    xpns_msg_debug "Index:${i} Argument:${args[i]}"
    _sep_count=$((_sep_count + 1))
    _exec_cmd="${_cmd_template//${_repstr}/${args[i]}}"

    ## Speedy mode
    if [[ $_speedy_flag -eq 1 ]]; then

      _exec_cmd=$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flag}" "${_await_flag}" "$i" "${_exec_cmd}")
      # Execute command as a child process of default-shell.
      ${TMUX_XPANES_EXEC} split-window -t "${_window_name}" -h -d "${_exec_cmd}"
    else
      # Open login shell and execute command on the interactive screen.
      ${TMUX_XPANES_EXEC} split-window -t "${_window_name}" -h -d
    fi
    # Restraining that size of pane's width becomes
    # less than the minimum size which is defined by tmux.
    if [[ ${_sep_count} -gt 2 ]]; then
      ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" tiled
    fi
  done
}

#
# Create new panes to the  existing window.
# Usage:
#    func <window name> <offset of index> <number of pane>
#
xpns_prepare_extra_panes() {
  local _window_name="$1"     ; shift
  local _pane_base_index="$1" ; shift
  local _log_flag="$1"        ; shift
  local _title_flag="$1"      ; shift
  local _speedy_flg="$1"      ; shift
  local _await_flg="$1"       ; shift
  # specify a pane which has the biggest index number.
  #   Because pane_id may not be immutable.
  #   If the small number of index is specified here, correspondance between pane_title and command can be slip off.
  ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}"

  # split window into multiple panes
  xpns_split_window \
    "${_window_name}" \
    "${_log_flag}" \
    "${_title_flag}" \
    "${_speedy_flg}" \
    "${_await_flg}" \
    "${_pane_base_index}" \
    "$@"
}

xpns_get_joined_begin_commands () {
  local _commands="$1"
  if [[ "${#XP_BEGIN_ARGS[*]}" -lt 1 ]]; then
    printf "%s" "${_commands}"
    return
  fi
  printf "%s\\n" "${XP_BEGIN_ARGS[@]}" "${_commands}"
}

xpns_inject_wait_command () {
  local _log_flag="$1"    ; shift
  local _title_flag="$1"  ; shift
  local _speedy_flg="$1"  ; shift
  local _await_flg="$1"   ; shift
  local _idx="$1"         ; shift
  local _exec_cmd="$1"    ; shift

  ## Speedy mode + logging
  if [[ "${_log_flag}" -eq 1 ]] && [[ "${_speedy_flg}" -eq 1 ]]; then
    # Wait for start of logging
    # Without this part, logging thread may start after new process is finished.
    # Execute function to wait for logging start.
    _exec_cmd="$(xpns_wait_func "log_${_window_name}-${_idx}-$$")"$'\n'"${_exec_cmd}"
  fi

  ## Speedy mode (Do not allow to close panes before the separation is finished).
  if [[ "${_speedy_flg}" -eq 1 ]]; then
    _exec_cmd="$(xpns_wait_func "sync_${_window_name}-${_idx}-$$")"$'\n'${_exec_cmd}
  fi

  ## -s: Speedy mode (Not -ss: Speedy mode + nowait)
  if [[ "${_await_flg}" -eq 1 ]]; then
    local _msg
    _msg="$(xpns_arr2args "${TMUX_XPANES_PANE_DEAD_MESSAGE}" | sed 's/"/\\"/g')"
    _exec_cmd="${_exec_cmd}"$'\n'"${XP_SHELL} -c \"printf -- ${_msg} >&2 && read\""
  fi
  printf "%s" "${_exec_cmd}"
}

xpns_new_window () {
  local _window_name="$1" ; shift
  local _attach_flg="$1"  ; shift
  local _speedy_flg="$1"  ; shift
  local _exec_cmd="$1"    ; shift
  local _window_id=

  # Create new window.
  if [[ "${_attach_flg}" -eq 1 ]]; then
    if [[ "${_speedy_flg}" -eq 1 ]]; then
      _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P "${_exec_cmd}")
    else
      _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P )
    fi
  else
    # Keep background
    if [[ "${_speedy_flg}" -eq 1 ]]; then
      _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P -d "${_exec_cmd}")
    else
      _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P -d)
    fi
  fi
  printf "%s" "${_window_id}"
}

xpns_new_pane_vertical () {
  local _window_id="$1"   ; shift
  local _cell_height="$1" ; shift
  local _speedy_flg="$1"  ; shift
  local _exec_cmd="$1"    ; shift
  local _pane_id=
  if [[ "${_speedy_flg}" -eq 1 ]]; then
    _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P "${_exec_cmd}")"
  else
    _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P)"
  fi
  printf "%s\\n" "${_pane_id}"
}

xpns_split_pane_horizontal () {
  local _target_pane_id="$1" ; shift
  local _cell_width="$1"     ; shift
  local _speedy_flg="$1"     ; shift
  local _exec_cmd="$1"       ; shift
  if [[ "${_speedy_flg}" -eq 1 ]]; then
    ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width" "${_exec_cmd}"
  else
    ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width"
  fi
}

xpns_prepare_window () {
  local _window_name="$1"     ; shift
  local _log_flag="$1"        ; shift
  local _title_flag="$1"      ; shift
  local _attach_flg="$1"      ; shift
  local _speedy_flg="$1"      ; shift
  local _await_flg="$1"       ; shift
  local _repstr="$1"          ; shift
  local _cmd_template="$1"    ; shift
  local _args=("$@")
  local _window_height="$XP_WINDOW_HEIGHT"
  local _window_width="$XP_WINDOW_WIDTH"
  local _col="$XP_OPT_CUSTOM_SIZE_COLS"
  local _row="$XP_OPT_CUSTOM_SIZE_ROWS"
  local _cols=("${XP_COLS[@]}")
  local _cols_offset=("${XP_COLS_OFFSETS[@]}")
  local _exec_cmd=
  local _pane_id=
  local _first_pane_id=
  local _window_id=
  local _cell_height=
  local _cell_width=
  local _top_pane_height=
  local _current_pane_width=
  local i=
  local j=
  local _rest_col=
  local _rest_row=
  local _offset=

  _cell_height=$(( ( _window_height - _row + 1 ) / _row ))
  ## Insert first element
  _exec_cmd="${_cmd_template//${_repstr}/${_args[0]}}"
  _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" 0 "${_exec_cmd}")"
  _window_id=$(xpns_new_window "${_window_name}" "${_attach_flg}" "${_speedy_flg}" "${_exec_cmd}")
  _first_pane_id=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p -F '#{pane_id}' | head -n 1)

  ## Start from last row
  for (( i = _row - 1 ; i > 0 ; i-- ));do
    _col="${_cols[i]}"
    _cell_width=$(( ( _window_width - _col + 1 ) / _col ))
    xpns_msg_debug "_col=$_col"
    (( _offset = _cols_offset[i] ))
    for (( j = 0 ; j < _col ; j++ ));do
      if (( j == 0 )) ;then
        (( idx = _offset - _col ))
        # Create new row
        # Insert first element of the row first
        _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
        _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
        _pane_id=$(xpns_new_pane_vertical "${_window_name}" "${_cell_height}" "${_speedy_flg}" "${_exec_cmd}")
      fi
      # Separate row into columns
      if (( j != 0 )) ;then
        (( idx = _offset - j ))
        _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
        _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
        ## Separate row into columns
        _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_pane_id" -p '#{pane_width}' | head -n 1)
        _rest_col=$(( _col - j + 1 ))
        _cell_width=$(( ( _current_pane_width - _rest_col + 1 ) / _rest_col ))
        xpns_split_pane_horizontal "$_pane_id" "$_cell_width" "${_speedy_flg}" "${_exec_cmd}"
      fi
    done

    # Adjust height
    _top_pane_height=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p '#{pane_height}' | head -n 1)
    _rest_row=$(( i ))
    xpns_msg_debug "_top_pane_height=$_top_pane_height _rest_row=$_rest_row"
    _cell_height=$(( ( _top_pane_height - _rest_row + 1 ) / _rest_row ))
  done

  # Split first row into columns
  _col="${_cols[0]}"
  _cell_width=$(( ( _window_width - _col + 1 ) / _col ))
  for (( j = 1 ; j < _col ; j++ ));do
    idx=$(( _cols_offset[0] - j ))
    # Adjust width
    _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_first_pane_id" -p '#{pane_width}' | head -n 1)
    _rest_col=$(( _col - j + 1 ))
    _cell_width=$(( ( _current_pane_width - _rest_col + 1 ) / _rest_col ))
    ## Split top row into columns
    _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
    _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
    xpns_split_pane_horizontal "${_first_pane_id}" "${_cell_width}" "${_speedy_flg}" "${_exec_cmd}"
  done
}

xpns_is_session_running() {
  local _socket="$1"
  ${TMUX_XPANES_EXEC} -S "${_socket}" list-session > /dev/null 2>&1
}

# Remove unnecessary session files as much as possible
# to let xpanes avoids to load old .tmux.conf.
xpns_clean_session() {
  if [[ "${XP_SOCKET_PATH}" != "${XP_DEFAULT_SOCKET_PATH}" ]]; then
    return
  fi
  # Delete old socket file (xpanes v3.1.0 or before).
  if [[ -e "${XP_DEFAULT_SOCKET_PATH_BASE}" ]]; then
    if ! xpns_is_session_running "${XP_DEFAULT_SOCKET_PATH_BASE}" ;then
      xpns_msg_debug "socket(${XP_DEFAULT_SOCKET_PATH_BASE}) is not running. Remove it"
      rm -f "${XP_DEFAULT_SOCKET_PATH_BASE}"
    fi
  fi
  for _socket in "${XP_CACHE_HOME}"/socket.* ;do
    xpns_msg_debug "file = ${_socket}"
    if ! xpns_is_session_running "${_socket}" ;then
      xpns_msg_debug "socket(${_socket}) is not running. Remove it"
      rm -f "${_socket}"
    else
      xpns_msg_debug "socket(${_socket}) is running. Keep ${_socket}"
    fi
  done
}

#
# Split a new window which was created by tmux into multiple panes.
# Usage:
#    xpns_prepare_preset_layout_window <window name> <offset of index> <number of pane> <attach or not>
#
xpns_prepare_preset_layout_window() {
  local _window_name="$1"     ; shift
  local _pane_base_index="$1" ; shift
  local _log_flag="$1"        ; shift
  local _title_flag="$1"      ; shift
  local _attach_flg="$1"      ; shift
  local _speedy_flg="$1"      ; shift
  local _await_flg="$1"       ; shift
  # Create new window.
  if [[ "${_attach_flg}" -eq 1 ]]; then
    ${TMUX_XPANES_EXEC} new-window -n "${_window_name}"
  else
    # Keep background
    ${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -d
  fi

  # specify a pane which has the youngest number of index.
  ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}"

  # split window into multiple panes
  xpns_split_window \
    "${_window_name}" \
    "${_log_flag}" \
    "${_title_flag}" \
    "${_speedy_flg}" \
    "${_await_flg}" \
    "${_pane_base_index}" \
    "$@"

  ### If the first pane is still remaining,
  ### panes cannot be organized well.
  # Delete the first pane
  ${TMUX_XPANES_EXEC} kill-pane -t "${_window_name}.${_pane_base_index}"

  # Select second pane here.
  #   If the command gets error, it would most likely be caused by user (XP_ENOPANE).
  #   Suppress error message here and announce it in xpns_execution.
  ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}" > /dev/null 2>&1
}

# Check whether given command is in the PATH or not.
xpns_check_env() {
  local _cmds="$1"
  while read -r cmd ; do
    if ! type "${cmd}" > /dev/null 2>&1; then
      if [[ "${cmd}" == "tmux" ]] && [[ "${TMUX_XPANES_EXEC}" == "tmux" ]]; then
        xpns_msg_error "${cmd} is required. Install ${cmd} or set TMUX_XPANES_EXEC variable."
        exit ${XP_ENOCMD}
      elif [[ "${cmd}" != "tmux" ]]; then
        xpns_msg_error "${cmd} is required."
        exit ${XP_ENOCMD}
      fi
    fi
  done < <(echo "${_cmds}" | tr ' ' '\n')

  if ! mkdir -p "${XP_CACHE_HOME}";then
    xpns_msg_warning "failed to create cache directory '${XP_CACHE_HOME}'."
  fi

  # Do not omit this part, this is used by testing.
  TMUX_XPANES_TMUX_VERSION="${TMUX_XPANES_TMUX_VERSION:-$(xpns_get_tmux_version)}"
  if ( xpns_tmux_is_greater_equals \
    "${XP_SUPPORT_TMUX_VERSION_LOWER}" \
    "${TMUX_XPANES_TMUX_VERSION}" ) ;then
    : "Supported tmux version"
  else
    xpns_msg_warning \
"'${XP_THIS_FILE_NAME}' may not work correctly! Please check followings.
* tmux is installed correctly.
* Supported tmux version is installed.
  Version ${XP_SUPPORT_TMUX_VERSION_LOWER} and over is officially supported."
  fi

  return 0
}

xpns_pipe_filter() {
  local _number="${1-}"
  if [[ -z "${_number-}" ]]; then
    cat
  else
    xargs -n "${_number}"
  fi
}

xpns_set_args_per_pane() {
  local _pane_num="$1"; shift
  local _filtered_args=()
  while read -r _line; do
    _filtered_args+=("${_line}")
  done < <(xargs -n "${_pane_num}" <<<"$(xpns_arr2args "${XP_ARGS[@]}")")
  XP_ARGS=("${_filtered_args[@]}")
}

xpns_get_window_height_width() {
  local _height=
  local _width=
  local _result=
  local _dev=
  local _pattern='^([0-9]+)[ \t]+([0-9]+)$'

  if ! type stty > /dev/null 2>&1; then
    xpns_msg_debug "'stty' does not exist: Failed to get window height and size. Skip checking"
    return 1
  fi

  ## This condition is used for unit testing
  if [[ -z "${XP_IS_PIPE_MODE-}" ]]; then
    if [[ ! -t 0 ]]; then
      XP_IS_PIPE_MODE=1
    fi
  fi
  if [[ $XP_IS_PIPE_MODE -eq 0 ]]; then
    if _result=$(stty size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
      _height="${BASH_REMATCH[1]}"
      _width="${BASH_REMATCH[2]}"
      xpns_msg_debug "window height: $_height, width: $_width"
      printf "%s\\n" "$_height $_width"
      return 0
    fi
  else
    if ! type ps > /dev/null 2>&1 ;then
      xpns_msg_debug "'ps' does not exist: Failed to get window height and size. Skip checking"
      return 1
    fi
    { read -r; read -r _dev; } < <(ps -o tty -p $$)
    ## If it's Linux, -F option is used
    if _result=$(stty -F "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
      _height="${BASH_REMATCH[1]}"
      _width="${BASH_REMATCH[2]}"
      xpns_msg_debug "window height: $_height, width: $_width"
      printf "%s\\n" "$_height $_width"
      return 0
    fi
    ## If it's BSD, macOS, -F option is used
    if _result=$(stty -f "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
      _height="${BASH_REMATCH[1]}"
      _width="${BASH_REMATCH[2]}"
      xpns_msg_debug "window height: $_height, width: $_width"
      printf "%s\\n" "$_height $_width"
      return 0
    fi
    return 1
  fi
  return 1
}

xpns_check_cell_size_bulk() {
  local _cell_num="$1"    ; shift
  local _bulk_cols="$1"   ; shift
  local _win_height="$1"  ; shift
  local _win_width="$1"   ; shift
  local _ignore_flag="$1" ; shift
  local _all_cols=()
  # shellcheck disable=SC2178
  local _cols=0
  local _rows=0
  local _sum_cell=0
  IFS="," read -r -a _all_cols <<< "${_bulk_cols}"
  _rows="${#_all_cols[@]}"
  for i in "${_all_cols[@]}"; do
    (( i >= _cols )) && (( _cols = i ))
    (( _sum_cell = _sum_cell + i ))
  done
  if (( _sum_cell != _cell_num )) ;then
    xpns_msg_error "Number of cols does not equals to the number of arguments."
    xpns_msg_error "Expected (# of args) : $_cell_num, Actual (--bulk-cols) : $_sum_cell)."
    return ${XP_ELAYOUT:-6}
  fi
  local cell_height=$(( ( _win_height - _rows + 1 ) / _rows ))
  local cell_width=$(( ( _win_width - _cols + 1 ) / _cols ))

  ## Display basic information
  xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }"
  xpns_msg_debug "Cell: { Height: $cell_height, Width: $cell_width }"
  xpns_msg_debug "# Of Panes: ${_cell_num}"
  xpns_msg_debug "         | Row[0] --...--> Row[MAX]"
  xpns_msg_debug "    -----+------------------------..."
  xpns_msg_debug "    Col[]| ${_all_cols[*]}"
  xpns_msg_debug "    -----+------------------------..."

  if [[ "$_ignore_flag" -ne 1 ]] && ( (( cell_height < 2 )) || (( cell_width < 2 )) ); then
    xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)"
    return ${XP_ESMLPANE:-7}
  fi
  printf "%s\\n" "${_cols} ${_rows} ${_all_cols[*]}"
}

xpns_check_cell_size() {
  local _cell_num="$1"    ; shift
  local _cols="$1"        ; shift
  local _rows="$1"        ; shift
  local _win_height="$1"  ; shift
  local _win_width="$1"   ; shift
  local _ignore_flag="$1" ; shift
  local _all_cols_num=
  local _all_rows=()

  if [[ -n "${_cols-}" ]] && [[ -n "${_rows-}" ]];then
    xpns_msg_warning "Both col size and row size are provided. Col size is preferentially going to be applied."
  fi
  ## if col is only defined
  if [[ -n "${_cols-}" ]] ;then
    read -r _cols _rows < <(xpns_adjust_col_row "${_cols-}" 0 "${_cell_num}")
    IFS=" " read -r -a _all_rows <<< "$(xpns_divide_equally "${_cell_num}" "${_cols}")"
    _all_cols_num="$(xpns_nums_transpose "${_all_rows[@]}")"

  ## if row is only defined
  elif [[ -n "${_rows-}" ]] ;then
    read -r _cols _rows < <(xpns_adjust_col_row 0 "${_rows-}" "${_cell_num}")
    _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")"

  ## if both are undefined
  else
    read -r _cols _rows < <(xpns_adjust_col_row 0 0 "${_cell_num}")
    _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")"
  fi

  local cell_height=$(( ( _win_height - _rows + 1 ) / _rows ))
  local cell_width=$(( ( _win_width - _cols + 1 ) / _cols ))

  ## Display basic information
  xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }"
  xpns_msg_debug "Cell: { Height: $cell_height, Width: $cell_width }"
  xpns_msg_debug "# Of Panes: ${_cell_num}"
  xpns_msg_debug "         | Row[0] --...--> Row[MAX]"
  xpns_msg_debug "    -----+------------------------..."
  xpns_msg_debug "    Col[]| ${_all_cols_num}"
  xpns_msg_debug "    -----+------------------------..."

  if [[ "$_ignore_flag" -ne 1 ]] && ( (( cell_height < 2 )) || (( cell_width < 2 )) ); then
    xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)"
    return "${XP_ESMLPANE:-7}"
  fi
  printf "%s\\n" "${_cols} ${_rows} ${_all_cols_num}"
}

# Execute from Normal mode1
xpns_pre_execution() {
  local _opts4args=""
  local _args4args=""

  if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
    xpns_msg_error "'-x' must be used within the running tmux session."
    exit ${XP_EINVAL}
  fi

  # Run as best effort.
  # Because after the tmux session is created, cols and rows would be provided by tmux.
  IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < <(xpns_get_window_height_width) && {
    local _arg_num="${#XP_ARGS[@]}"
    local _cell_num _tmp_col_row_cols _tmp_cols
    if [[ -n "$XP_MAX_PANE_ARGS" ]] && (( XP_MAX_PANE_ARGS > 1 ));then
      _cell_num=$(( _arg_num / XP_MAX_PANE_ARGS ))
    else
      _cell_num="${_arg_num}"
    fi
    if [[ -n "${XP_OPT_BULK_COLS}" ]]; then
      _tmp_col_row_cols="$(xpns_check_cell_size_bulk \
        "${_cell_num}" \
        "${XP_OPT_BULK_COLS}" \
        "${XP_WINDOW_HEIGHT}" \
        "${XP_WINDOW_WIDTH}" \
        "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
      local _exit_status="$?"
      [[ $_exit_status -eq ${XP_ELAYOUT} ]] && exit ${XP_ELAYOUT}
      [[ $_exit_status -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
    else
      _tmp_col_row_cols="$(xpns_check_cell_size \
        "${_cell_num}" \
        "${XP_OPT_CUSTOM_SIZE_COLS-}" \
        "${XP_OPT_CUSTOM_SIZE_ROWS-}" \
        "${XP_WINDOW_HEIGHT}" \
        "${XP_WINDOW_WIDTH}" \
        "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
      [[ $? -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
    fi

    IFS=" " read -r XP_OPT_CUSTOM_SIZE_COLS XP_OPT_CUSTOM_SIZE_ROWS _tmp_cols <<< "$_tmp_col_row_cols"
    IFS=" " read -r -a XP_COLS <<< "${_tmp_cols}"
    IFS=" " read -r -a XP_COLS_OFFSETS <<< "$(printf "%s\\n" "${XP_COLS[*]}" | xpns_nums_accumulate_sum)"
    xpns_msg_debug "Options: $(xpns_arr2args "${XP_OPTIONS[@]}")"
    xpns_msg_debug "Arguments: $(xpns_arr2args "${XP_ARGS[@]}")"
  }

  # Append -- flag.
  # Because any arguments may have `-`
  if [[ ${XP_NO_OPT} -eq 1 ]]; then
    XP_ARGS=("--" "${XP_ARGS[@]}")
  fi

  # If there is any options, escape them.
  if [[ -n "${XP_OPTIONS[*]-}" ]]; then
    _opts4args=$(xpns_arr2args "${XP_OPTIONS[@]}")
  fi
  _args4args=$(xpns_arr2args "${XP_ARGS[@]}")

  # Run as best effort
  xpns_clean_session || true

  # Create new session.
  ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" new-session \
    -s "${XP_SESSION_NAME}" \
    -n "${XP_TMP_WIN_NAME}" \
    -d "${XP_ABS_THIS_FILE_NAME} ${_opts4args} ${_args4args}"

  # Avoid attaching (for unit testing).
  if [[ ${XP_OPT_ATTACH} -eq 1 ]]; then
    if ! ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" attach-session -t "${XP_SESSION_NAME}" \
      && [[ ${XP_IS_PIPE_MODE} -eq 1 ]]; then
      ## In recovery case, overwrite trap to keep socket file
      trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$;' EXIT

      xpns_msg "Recovery" \
"Execute below command line to re-attach the new session.

${TMUX_XPANES_EXEC} -S ${XP_SOCKET_PATH} attach-session -t ${XP_SESSION_NAME}

"
      exit ${XP_ETTY}
    fi
  fi
}

# Execute from inside of tmux session
xpns_execution() {
  local _pane_base_index=
  local _window_name=
  local _last_args_idx=
  local _def_allow_rename=
  local _pane_count=0

  if [[ ${XP_IS_PIPE_MODE} -eq 0 ]] && [[ -n "${XP_MAX_PANE_ARGS-}" ]];then
    xpns_set_args_per_pane "${XP_MAX_PANE_ARGS}"
  fi

  ## Fix window size and define pane size
  {
    local  _tmp_col_row_cols _tmp_cols
    IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < \
      <(${TMUX_XPANES_EXEC} display-message -p '#{window_height} #{window_width}')
    if [[ -n "${XP_OPT_BULK_COLS}" ]]; then
      _tmp_col_row_cols="$(xpns_check_cell_size_bulk \
        "${#XP_ARGS[@]}" \
        "${XP_OPT_BULK_COLS}" \
        "${XP_WINDOW_HEIGHT}" \
        "${XP_WINDOW_WIDTH}" \
        "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
      local _exit_status="$?"
      [[ $_exit_status -eq ${XP_ELAYOUT} ]] && exit ${XP_ELAYOUT}
      [[ $_exit_status -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
    else
      _tmp_col_row_cols="$(xpns_check_cell_size \
        "${#XP_ARGS[@]}" \
        "${XP_OPT_CUSTOM_SIZE_COLS-}" \
        "${XP_OPT_CUSTOM_SIZE_ROWS-}" \
        "${XP_WINDOW_HEIGHT}" \
        "${XP_WINDOW_WIDTH}" \
        "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
      [[ $? -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
    fi
    IFS=" " read -r XP_OPT_CUSTOM_SIZE_COLS XP_OPT_CUSTOM_SIZE_ROWS _tmp_cols <<< "$_tmp_col_row_cols"
    IFS=" " read -r -a XP_COLS <<< "${_tmp_cols}"
    IFS=" " read -r -a XP_COLS_OFFSETS <<< "$(printf "%s\\n" "${XP_COLS[*]}" | xpns_nums_accumulate_sum)"
    xpns_msg_debug "Options: $(xpns_arr2args "${XP_OPTIONS[@]}")"
    xpns_msg_debug "Arguments: $(xpns_arr2args "${XP_ARGS[@]}")"
  }

  _pane_base_index=$(xpns_get_global_tmux_conf 'pane-base-index')
  _last_args_idx=$((${#XP_ARGS[@]} - 1))
  _def_allow_rename="$(xpns_get_global_tmux_conf 'allow-rename')"

  xpns_suppress_allow_rename "${_def_allow_rename-}"
  XP_CMD_UTILITY="$(xpns_get_joined_begin_commands "${XP_CMD_UTILITY}")"

  if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
    # Reuse existing window name
    # tmux 1.6 does not support -F option
    _window_name="$( ${TMUX_XPANES_EXEC} display -p -F "#{window_id}" )"
    _pane_count="$( ${TMUX_XPANES_EXEC} list-panes | grep -c . )"
    _pane_base_index=$(( _pane_base_index + _pane_count - 1 ))
    _pane_active_pane_id=$(${TMUX_XPANES_EXEC} display -p -F "#{pane_id}")
  else
    _window_name=$(
      xpns_generate_window_name \
        "${XP_EMPTY_STR}" \
        "${XP_ARGS[0]}" \
        | xpns_value2key)
  fi

  ## --------------------
  # Prepare window and panes
  ## --------------------
  if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
    xpns_prepare_extra_panes \
      "${_window_name}" \
      "${_pane_base_index}" \
      "${XP_OPT_LOG_STORE}" \
      "${XP_OPT_SET_TITLE}" \
      "${XP_OPT_SPEEDY}" \
      "${XP_OPT_SPEEDY_AWAIT}" \
      "${XP_REPSTR}" \
      "${XP_CMD_UTILITY}" \
      "${XP_ARGS[@]}"
  elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 1 ]];then
    xpns_prepare_preset_layout_window \
      "${_window_name}" \
      "${_pane_base_index}" \
      "${XP_OPT_LOG_STORE}" \
      "${XP_OPT_SET_TITLE}" \
      "${XP_OPT_ATTACH}" \
      "${XP_OPT_SPEEDY}" \
      "${XP_OPT_SPEEDY_AWAIT}" \
      "${XP_REPSTR}" \
      "${XP_CMD_UTILITY}" \
      "${XP_ARGS[@]}"
  elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 0 ]];then
    xpns_prepare_window \
      "${_window_name}" \
      "${XP_OPT_LOG_STORE}" \
      "${XP_OPT_SET_TITLE}" \
      "${XP_OPT_ATTACH}" \
      "${XP_OPT_SPEEDY}" \
      "${XP_OPT_SPEEDY_AWAIT}" \
      "${XP_REPSTR}" \
      "${XP_CMD_UTILITY}" \
      "${XP_ARGS[@]}"
  fi

  ## With -ss option, it is possible to close all the panes as of here.
  ## Check status of the window. If no window exists, there is nothing to do execpt to exit.
  xpns_msg_debug "xpns_is_window_alive:1: After window separation"
  xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"

  if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
    # Set offset to avoid sending command to the original pane.
    _pane_base_index=$((_pane_base_index + 1))
    # Avoid to make layout even-horizontal even if there are many panes.
    # in xpns_organize_panes
    _last_args_idx=$((_last_args_idx + _pane_count))
    # Re-select the windown that was active before.
    ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_active_pane_id}"
  fi

  if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then
    xpns_enable_logging \
      "${_window_name}" \
      "${_pane_base_index}" \
      "${TMUX_XPANES_LOG_DIRECTORY}" \
      "${TMUX_XPANES_LOG_FORMAT}" \
      "${XP_EMPTY_STR}" \
      "${XP_ARGS[@]}"

    if [[ $XP_OPT_SPEEDY -eq 1 ]]; then
      xpns_notify_logging \
        "${_window_name}" \
        "${XP_ARGS[@]}"
    fi
  fi

  xpns_msg_debug "xpns_is_window_alive:2: After logging"
  xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"

  # Set pane titles for each pane.
  if xpns_is_pane_title_required "${XP_OPT_SET_TITLE}" "${XP_OPT_EXTRA}" ;then
    xpns_set_titles \
      "${_window_name}" \
      "${_pane_base_index}" \
      "${XP_ARGS[@]}"
  fi

  if [[ $XP_OPT_SPEEDY -eq 1 ]];then
    xpns_notify_sync \
      "${_window_name}" \
      "${XP_ARGS[@]}"
  fi

  xpns_msg_debug "xpns_is_window_alive:3: After setting title"
  xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"

  # Sending operations for each pane.
  # With -s option, command is already sent.
  if [[ $XP_OPT_SPEEDY -eq 0 ]]; then
    xpns_send_commands \
      "${_window_name}" \
      "${_pane_base_index}" \
      "${XP_REPSTR}" \
      "${XP_CMD_UTILITY}" \
      "${XP_ARGS[@]}"
  fi

  xpns_msg_debug "xpns_is_window_alive:4: After sending commands"
  xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"

  ## With -l <layout>, panes are organized.
  ## As well as -x, they are re-organized.
  if [[ $XP_OPT_USE_PRESET_LAYOUT -eq 1 ]] || [[ ${XP_OPT_EXTRA} -eq 1 ]]; then
    xpns_organize_panes \
      "${_window_name}" \
      "${_last_args_idx}"
  fi

  # Enable broadcasting
  if [[ ${XP_OPT_IS_SYNC} -eq 1 ]] && [[ ${XP_OPT_EXTRA} -eq 0 ]]; then
    ${TMUX_XPANES_EXEC} \
      set-window-option -t "${_window_name}" \
      synchronize-panes on
  fi

  ## In case of -t option
  if [[ ${XP_OPT_SET_TITLE} -eq 1 ]] && [[ ${XP_OPT_CHANGE_BORDER} -eq 1 ]]; then
    # Set border format
    ${TMUX_XPANES_EXEC} \
      set-window-option -t "${_window_name}" \
      pane-border-format "${TMUX_XPANES_PANE_BORDER_FORMAT}"
    # Show border status
    ${TMUX_XPANES_EXEC} \
      set-window-option -t "${_window_name}" \
      pane-border-status "${TMUX_XPANES_PANE_BORDER_STATUS}"
  fi

  # In case of -x, this statement is skipped to keep the original window name
  if [[ ${XP_OPT_EXTRA} -eq 0 ]];then
    # Restore original window name.
    ${TMUX_XPANES_EXEC} \
      rename-window -t "${_window_name}" \
      -- "$(printf "%s\\n" "${_window_name}" | xpns_key2value)"
  fi

  xpns_restore_allow_rename "${_def_allow_rename-}"
}

## ----------------
# Arrange options for pipe mode
#  * argument -> command
#  * stdin -> argument
## ----------------
xpns_switch_pipe_mode() {
  local _pane_num4new_term=""
  if [[ -n "${XP_ARGS[*]-}" ]] && [[ -n "${XP_CMD_UTILITY-}" ]]; then
    xpns_msg_error "Both arguments and other options (like '-c', '-e') which updates <command> are given."
    exit ${XP_EINVAL}
  fi

  if [[ -z "${TMUX-}" ]]; then
    xpns_msg_warning "Attached session is required for 'Pipe mode'."
    # This condition is used when the following situations.
    #   * Enter from outside of tmux session(Normal mode1)
    #   * Pipe mode.
    #   * -n option.
    #
    # For example:
    #     (Normal mode1)$ echo {a..g} | ./xpanes -n 2
    # => This will once create the new window like this.
    #     (inside of tmux session)$ ./xpanes '-n' '2' 'a' 'b' 'c' 'd' 'e' 'f' 'g'
    #     => After the window is closed, following panes would be left.
    #     (pane 1)$ echo a b
    #     (pane 2)$ echo c d
    #     (pane 3)$ echo e f
    #     (pane 4)$ echo g
    # In order to create such the query,
    # separate all the argument into minimum tokens
    # with xargs -n 1
    if [[ -n "${XP_MAX_PANE_ARGS-}" ]]; then
      _pane_num4new_term=1
    fi
  fi

  while read -r line;
  do
    XP_STDIN+=("${line}")
  done < <(cat | xpns_rm_empty_line | \
    xpns_pipe_filter "${_pane_num4new_term:-${XP_MAX_PANE_ARGS}}")


  # Merge them into command.
  if [[ -n "${XP_ARGS[*]-}" ]]; then
    # Attention: It might be wrong result if IFS is changed.
    XP_CMD_UTILITY="${XP_ARGS[*]}"
  fi

  # If there is empty -I option or user does not assign the <repstr>,
  # Append the space and <repstr> at the end of the <command>
  # This is same as the xargs command of GNU.
  # i.e,
  #   $ echo 10 | xargs seq
  #     => seq 10
  # Whith is same as
  #   $ echo 10 | xargs -I@ seq @
  #     => seq 10
  if [[ -z "${XP_REPSTR}" ]]; then
    XP_REPSTR="${XP_DEFAULT_REPSTR}"
    if [[ -n "${XP_ARGS[*]-}" ]]; then
      XP_CMD_UTILITY="${XP_ARGS[*]-} ${XP_REPSTR}"
    fi
  fi

  # Deal with stdin as arguments.
  XP_ARGS=("${XP_STDIN[@]-}")
}

xpns_layout_short2long() {
  sed \
    -e 's/^t$/tiled/' \
    -e 's/^eh$/even-horizontal/' \
    -e 's/^ev$/even-vertical/' \
    -e 's/^mh$/main-horizontal/' \
    -e 's/^mv$/main-vertical/' \
    -e ';'
}

xpns_is_valid_layout() {
  local _layout="${1-}"
  local _pat='^(tiled|even-horizontal|even-vertical|main-horizontal|main-vertical)$'
  if ! [[ $_layout =~ $_pat ]]  ; then
    xpns_msg_error "Invalid layout '${_layout}'."
    exit ${XP_ELAYOUT}
  fi
}

xpns_warning_before_extra() {
  local _ans=
  local _synchronized=
  _synchronized="$(xpns_get_local_tmux_conf "synchronize-panes")"
  if [[ "on" == "${_synchronized}" ]];then
    xpns_msg_warning "Panes are now synchronized.
'-x' option may cause unexpected behavior on the synchronized panes."
    printf "Are you really sure? [y/n]: "
    read -r _ans
    if ! [[ "${_ans-}" =~ ^[yY]  ]]; then
      return 1
    fi
  fi
}

xpns_load_flag_options() {
  if [[ "$1" =~ h ]]; then
    xpns_usage
    exit 0
  fi
  if [[ "$1" =~ V ]]; then
    xpns_version
    exit 0
  fi
  if [[ "$1" =~ x ]]; then
    XP_OPT_EXTRA=1
    XP_OPT_USE_PRESET_LAYOUT=1 ## Layout presets must be used with -x
    if ! xpns_warning_before_extra; then
      exit ${XP_EINTENT}
    fi
  fi
  if [[ "$1" =~ d ]]; then
    XP_OPT_IS_SYNC=0
  fi
  if [[ "$1" =~ e ]]; then
    XP_REPSTR="{}"
    XP_CMD_UTILITY="{}"
  fi
  if [[ "$1" =~ t ]]; then
    if ( xpns_tmux_is_greater_equals 2.3 ) ; then
      XP_OPT_SET_TITLE=1
      XP_OPT_CHANGE_BORDER=1
    else
      xpns_msg_warning "-t option cannot be used by tmux version less than 2.3. Disabled."
      sleep 1
    fi
  fi
  if [[ "$1" =~ s ]]; then
    XP_OPT_SPEEDY=1
    XP_OPT_SPEEDY_AWAIT=1
  fi
  if [[ "$1" =~ ss ]]; then
    XP_OPT_SPEEDY_AWAIT=0
  fi
  return 1
}

xpns_load_arg_options() {
  # Extract flag options only.
  local _pattern=
  xpns_load_flag_options "$(xpns_extract_matched "$1" "^-${XP_FLAG_OPTIONS}+")" > /dev/null
  if [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*I ]]; then
    # Behavior like this.
    # -IAAA       -- XP_REPSTR="AAA"
    # -I AAA BBB  -- XP_REPSTR="AAA", XP_ARGS=("BBB")
    # -I"AAA BBB" -- XP_REPSTR="AAA BBB"
    # -IAAA BBB   -- XP_REPSTR="AAA", XP_ARGS=("BBB")
    # -I -d ...   -- XP_REPSTR=""
    _pattern="^-${XP_FLAG_OPTIONS}*I(.+)"
    if [[ "$1" =~ $_pattern ]]; then
      XP_REPSTR="${BASH_REMATCH[1]}"
      return 0
    elif ! [[ "$2" =~ ^-.* ]]; then
      XP_REPSTR="$2"
      return 0
    else
      xpns_msg_error "invalid argument '$2' for -I option"
      exit ${XP_EINVAL}
    fi
  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*l ]]; then
    _pattern="^-${XP_FLAG_OPTIONS}*l(.+)"
    if [[ "$1" =~ $_pattern ]]; then
      XP_OPT_USE_PRESET_LAYOUT=1
      XP_LAYOUT="$(cat <<<"${BASH_REMATCH[1]}" | xpns_layout_short2long)"
      xpns_is_valid_layout "${XP_LAYOUT}"
      return 0
    elif ! [[ "$2" =~ ^-.* ]]; then
      XP_OPT_USE_PRESET_LAYOUT=1
      XP_LAYOUT="$(cat <<<"$2" |  xpns_layout_short2long )"
      xpns_is_valid_layout "${XP_LAYOUT}"
      return 0
    else
      xpns_msg_error "invalid argument '$2' for -l option"
      exit ${XP_EINVAL}
    fi
  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*c ]]; then
    _pattern="^-${XP_FLAG_OPTIONS}*c(.+)"
    if [[ "$1" =~ $_pattern ]]; then
      XP_CMD_UTILITY="${BASH_REMATCH[1]}"
      XP_OPT_CMD_UTILITY=1
      return 0
    elif ! [[ "$2" =~ ^-.* ]]; then
      XP_CMD_UTILITY="$2"
      XP_OPT_CMD_UTILITY=1
      return 0
    else
      xpns_msg_error "invalid argument '$2' for -c option"
      exit ${XP_EINVAL}
    fi
  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*n ]]; then
    _pattern="^-${XP_FLAG_OPTIONS}*n([0-9]+)"
    if [[ "$1" =~ $_pattern ]]; then
      XP_MAX_PANE_ARGS="${BASH_REMATCH[1]}"
      return 0
    elif [[ "$2" =~ ^[0-9]+$ ]]; then
      XP_MAX_PANE_ARGS="$2"
      return 0
    else
      xpns_msg_error "invalid argument '$2' for -n option"
      exit ${XP_EINVAL}
    fi
  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*S ]]; then
    _pattern="^-${XP_FLAG_OPTIONS}*S(.+)"
    if [[ "$1" =~ $_pattern ]]; then
      XP_SOCKET_PATH="${BASH_REMATCH[1]}"
      return 0
    elif ! [[ "$2" =~ ^-.* ]]; then
      XP_SOCKET_PATH="$2"
      return 0
    else
      xpns_msg_error "invalid argument '$2' for -S option"
      exit ${XP_EINVAL}
    fi
  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*C ]]; then
    _pattern="^-${XP_FLAG_OPTIONS}*C([0-9]+)"
    if [[ "$1" =~ $_pattern ]]; then
      XP_OPT_CUSTOM_SIZE_COLS="${BASH_REMATCH[1]}"
      return 0
    elif [[ "$2" =~ ^[0-9]+$ ]];then
      XP_OPT_CUSTOM_SIZE_COLS="$2"
      return 0
    else
      xpns_msg_error "invalid argument '$2' for -C option"
      exit ${XP_EINVAL}
    fi
  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*R ]]; then
    _pattern="^-${XP_FLAG_OPTIONS}*R([0-9]+)"
    if [[ "$1" =~ $_pattern ]]; then
      XP_OPT_CUSTOM_SIZE_ROWS="${BASH_REMATCH[1]}"
      return 0
    elif [[ "$2" =~ ^[0-9]+$ ]];then
      XP_OPT_CUSTOM_SIZE_ROWS="$2"
      return 0
    else
      xpns_msg_error "invalid argument '$2' for -R option"
      exit ${XP_EINVAL}
    fi
  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*B ]]; then
    _pattern="^-${XP_FLAG_OPTIONS}*B(.+)"
    if [[ "$1" =~ $_pattern ]]; then
      XP_BEGIN_ARGS+=("${BASH_REMATCH[1]}")
      return 0
    else
      XP_BEGIN_ARGS+=("$2")
      return 0
    fi
  fi
  return 0
}

xpns_load_long_options() {
  if [[ "$1" =~ ^--help$ ]]; then
    xpns_usage
    exit 0
  elif [[ "$1" =~ ^--version$ ]]; then
    xpns_version
    exit 0
  elif [[ "$1" =~ ^--desync$ ]]; then
    XP_OPT_IS_SYNC=0
    return 1
  elif [[ "$1" =~ ^--log-format=.*$ ]]; then
    XP_OPT_LOG_STORE=1
    TMUX_XPANES_LOG_FORMAT="${1#--log-format=}"
    return 1
  elif [[ "$1" =~ ^--log ]]; then
    XP_OPT_LOG_STORE=1
    if [[ "$1" =~ ^--log=.*$  ]]; then
      TMUX_XPANES_LOG_DIRECTORY="${1#--log=}"
    fi
    return 1
  elif [[ "$1" =~ ^--ssh$ ]]; then
    XP_CMD_UTILITY="${XP_SSH_CMD_UTILITY}"
    # Enable -t option as well
    XP_OPT_SET_TITLE=1
    XP_OPT_CHANGE_BORDER=1
    # Enable -s option
    XP_OPT_SPEEDY=1
    XP_OPT_SPEEDY_AWAIT=1
    return 1
  elif [[ "$1" =~ ^--stay$ ]]; then
    XP_OPT_ATTACH=0
    return 1
  elif [[ "$1" =~ ^--cols=[0-9]+$ ]]; then
    XP_OPT_CUSTOM_SIZE_COLS="${1#--cols=}"
    return 1
  elif [[ "$1" =~ ^--rows=[0-9]+$ ]]; then
    XP_OPT_CUSTOM_SIZE_ROWS="${1#--rows=}"
    return 1
  elif [[ "$1" =~ ^--bulk-cols=[0-9,]*[0-9]+$ ]]; then
    XP_OPT_BULK_COLS="${1#--bulk-cols=}"
    return 1
  elif [[ "$1" =~ ^--debug$ ]]; then
    XP_OPT_DEBUG=1
    return 1
  elif [[ "$1" =~ ^--dry-run$ ]]; then # For unit testing
    XP_OPT_DRY_RUN=1
    return 1
  elif [[ "$1" =~ ^--ignore-size-limit$ ]]; then
    XP_OPT_IGNORE_SIZE_LIMIT=1
    return 1

  ## ----------------
  # Other options
  ## ----------------
  else
    xpns_msg_error "invalid option -- '${1#--}'"
    xpns_usage_warn
    exit ${XP_EINVAL}
  fi
}

xpns_parse_options() {
  while (( $# > 0 )); do
    case "$1" in
      --)
      if [[ ${XP_NO_OPT} -eq 1 ]]; then
        XP_ARGS+=("$1")
        shift
      else
        # Disable any more options
        XP_NO_OPT=1
        shift
      fi
      ;;
      ## ----------------
      # Long options
      ## ----------------
      --*)
      if [[ ${XP_NO_OPT} -eq 1 ]]; then
        XP_ARGS+=("$1")
        shift
      else
        local _shift_count="0"
        xpns_load_long_options "$@"
        _shift_count="$?"
        [[ "${_shift_count}" = "1" ]] && XP_OPTIONS+=("$1") && shift
      fi
      ;;
      ## ----------------
      # Short options
      ## ----------------
      -*)
      if [[ ${XP_NO_OPT} -eq 1 ]]; then
        XP_ARGS+=("$1")
        shift
      else
        local _shift_count="0"
        if [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*${XP_ARG_OPTIONS}. ]];then
          xpns_load_arg_options "$@"
          XP_OPTIONS+=("$1") && shift
        elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*${XP_ARG_OPTIONS}$ ]] && [[ -n "${2-}" ]];then
          xpns_load_arg_options "$@"
          _shift_count="$?"
          XP_OPTIONS+=("$1" "$2") && shift && shift
        elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}+$  ]];then
          xpns_load_flag_options "$1"
          XP_OPTIONS+=("$1") && shift
        ## ----------------
        # Other options
        ## ----------------
        else
          xpns_msg_error "Invalid option -- '${1#-}'"
          xpns_usage_warn
          exit ${XP_EINVAL}
        fi
      fi
      ;;
      ## ----------------
      # Other arguments
      ## ----------------
      *)
      XP_ARGS+=("$1")
      XP_NO_OPT=1
      shift
      ;;
    esac
  done

  # If there is any standard input from pipe,
  # 1 line handled as 1 argument.
  if [[ ! -t 0 ]]; then
    XP_IS_PIPE_MODE=1
    xpns_switch_pipe_mode
  fi

  # When no argument arr given, exit.
  if [[ -z "${XP_ARGS[*]-}" ]]; then
    xpns_msg_error "No arguments."
    xpns_usage_warn
    exit ${XP_EINVAL}
  fi

  if [[ -n "${XP_OPT_CUSTOM_SIZE_COLS-}" ]] || [[ -n "${XP_OPT_CUSTOM_SIZE_ROWS-}" ]]; then
    if [[ "$XP_OPT_EXTRA" -eq 1 ]]; then
      xpns_msg_warning "The columns/rows options (-C, --cols, -R, --rows) cannot be used with -x option. Ignored."
    elif [[ "$XP_OPT_EXTRA" -eq 0 ]] && [[ "${XP_OPT_USE_PRESET_LAYOUT}" -eq 1 ]]; then
      # This part is required to keep backward compatibility.
      ## Users can simulate xpanes v3.x to set : alias xpanes="xpanes -lt"
      xpns_msg_info "Columns/rows option (-C, --cols, -R, --rows) and -l option are provided. Disable -l. "
      XP_OPT_USE_PRESET_LAYOUT=0
    fi
  fi

  # Set default value in case of empty.
  XP_CMD_UTILITY="${XP_CMD_UTILITY:-${XP_DEFAULT_CMD_UTILITY}}"
  XP_REPSTR="${XP_REPSTR:-${XP_DEFAULT_REPSTR}}"

  # To set command on pre_execution, set -c option manually.
  if [[ ${XP_OPT_CMD_UTILITY} -eq 0 ]];then
    XP_OPTIONS+=("-c" "${XP_CMD_UTILITY}")
  fi

}

## --------------------------------
# Main function
## --------------------------------
xpns_main() {
  xpns_parse_options ${1+"$@"}
  xpns_check_env "${XP_DEPENDENCIES}"
  ## --------------------------------
  # Parameter validation
  ## --------------------------------
  # When do dry-run flag is enabled, skip running (this is used to execute unit test of itself).
  if [[ ${XP_OPT_DRY_RUN} -eq 1 ]]; then
    return 0
  fi
  # Validate log directory.
  if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then
    TMUX_XPANES_LOG_DIRECTORY=$(xpns_normalize_directory "${TMUX_XPANES_LOG_DIRECTORY}")
    xpns_is_valid_directory "${TMUX_XPANES_LOG_DIRECTORY}" && \
    TMUX_XPANES_LOG_DIRECTORY=$(cd "${TMUX_XPANES_LOG_DIRECTORY}" && pwd)
  fi
  ## --------------------------------
  # If current shell is outside of tmux session.
  ## --------------------------------
  if [[ -z "${TMUX-}" ]]; then
    xpns_pre_execution
  ## --------------------------------
  # If current shell is already inside of tmux session.
  ## --------------------------------
  else
    xpns_execution
  fi
  exit 0
}

## --------------------------------
# Entry Point
## --------------------------------
xpns_main ${1+"$@"}
