#!/bin/zsh
# vim: sts=2 sw=2 et
setopt no_unset warn_create_global
zmodload zsh/system || exit $?

typeset -g map=/run/display-container-map
typeset -g scandir=/run/service
typeset -g build=/run/xpra.service

main() {
  typeset -g container client_display
  container=$1
  shift || exit 100
  [[ $container == */* ]] && exit 100

  client_display=${${DISPLAY%%.*}#:}
  do_lock
  find_server_display
  ensure_services
  do_unlock
  start_services
  print_cookie
}
typeset -f -t main

do_lock() {
  mkdir -p $map || exit $?
  touch $map/.lock || exit $?
  zsystem flock -f lockfd $map/.lock || exit $?
}

find_server_display() {
  typeset -g display
  local -a found allocated
  found=( $map/*=$container(N) )

  if ! (($#found)); then
    # spawn a new Xpra session this container.
    allocated=( $map/*=*(N) )
    allocated=( ${${allocated:t}%%=*} )
    #for n in {${${DISPLAY%%.*}#:}00..${${DISPLAY%%.*}#:}99}; do
    for n in {100..199}; do
      if (( $allocated[(I)$n] == 0 )); then
        display=$n
        touch $map/$display=$container
        break
      fi
    done
    if [[ -z $display ]]; then
      exit 3  # All 99 displays occupied?!
    fi
  else
    display=${${found[1]:t}%%=*}
  fi
}

ensure_services() {
  typeset -ga new_services client_services
  typeset -g sv_name_xorg=xpra-xorg.$display
  typeset -g sv_name_server=xpra-server.$display
  typeset -g sv_name_client=xpra-client.$display.for.$client_display
  write_sv_definitions
  # if (($#new_services)); then
  #   - s6-svscanctl -a /run/service
  #   # TODO: wait for s6-supervises to start
  # fi
}

### Color and text definition {{{1

typeset -g hl_fatal hl_reset

# die messages
if (( $terminfo[colors] >= 8 )); then
        hl_fatal='%F{red}%B'; hl_fatal=${(%)hl_fatal}
        hl_warn='%F{yellow}%B'; hl_warn=${(%)hl_warn}
        hl_reset='%b%f'; hl_reset=${(%)hl_reset}
fi

### Utility functions {{{1
err_msg() {
  local first=$1
  shift
  printf >&2 '%s%s%s%s\n' "$hl_fatal" "$first" "$hl_reset" "$*"
}

warn_msg() {
  local first=$1
  shift
  printf >&2 '%s%s%s%s\n' "$hl_warn" "$first" "$hl_reset" "$*"
}

# helper that prints out stack, error message and exits
die_ret() {
  set +x
  local ret n
  ret=$1
  shift
  print -r - >&2 "${hl_fatal}Fatal$hl_reset error occurend in:"
  for n in {${#funcfiletrace}..1}; do
    printf >&2 '%d> %s (%s)\n' $n "$funcfiletrace[$n]" "$functrace[$n]"
  done
  printf >&2 '%s\n' "${hl_fatal}*$hl_reset $^@"
  exit $ret
}

die() {
  set +x
  die_ret 1 "$@"
}
die100() {  # 100: wrong usage
  set +x
  die_ret 100 "$@"
}
die111() {  # 111: system call failed
  set +x
  die_ret 111 "$@"
}

-() {  # Run command and die on nonzero exitcode
  "$@" || die_ret $? "command failed with exitcode $?: ${(j: :)${(q)@}}"
}

# service builder

sv_start() {
  (($+sv_name)) && \
    die100 "sv_start: previous service definition ${(qqq)sv_name} wasn't ended properly"
  (( $# != 1 )) && die100 "sv_start: incorrect arguments"
  [[ $1 == */* ]] && die100 "sv_start: incorrect argument"

  typeset -g sv_name=$1
  typeset -g sv_dir=$build/$1
  - mkdir -p $sv_dir
  - touch $sv_dir/down
}

sv_end() {
  (($+sv_name)) || die100 "$0: no service definition started"
  (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
  (( $# != 0 )) && die100 "$0: incorrect arguments"

  if ! [[ -L $scandir/$sv_name ]]; then
    - s6-svlink -t 3000 $scandir $sv_dir
    # - s6-mkfifodir $sv_dir/event
    # - ln -s -T $sv_dir $scandir/$sv_name
    # new_services+=( $scandir/$sv_name )
  fi
  unset sv_name sv_dir
}

sv_link_command() {
  (($+sv_name)) || die100 "$0: no service definition started"
  (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
  (( $# != 2 )) && die100 "$0: incorrect arguments"

  local script_path=$sv_dir/$1
  - ln -s -f -T ${commands[$2]} $script_path
}

sv_write_lines() {
  (($+sv_name)) || die100 "$0: no service definition started"
  (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
  (( $# < 2 )) && die100 "$0: incorrect arguments"

  local file_path=$sv_dir/$1
  shift
  printf '%s\n' >$file_path "$@" || \
    die111 "Error writing to ${(qqq)file_path}"
}

sv_el_script() {
  (($+sv_name)) || die100 "$0: no service definition started"
  (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"

  - sv_write_lines $1 '#!/bin/execlineb -P' "$@[2,-1]"
  - chmod +x $sv_dir/$1
}

sv_notification_fd() {
  (($+sv_name)) || die100 "$0: no service definition started"
  (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
  (( $# != 1 )) && die100 "$0: incorrect arguments"

  - sv_write_lines notification-fd $1
}

sv_env() {
  (($+sv_name)) || die100 "$0: no service definition started"
  (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
  (( $# < 2 )) && die100 "$0: incorrect arguments"

  - mkdir -p $sv_dir/env
  local k v
  for k v in "$@"; do
    [[ $k == */* ]] && die100 "$0: invalid env variable ${(qqq)k}"
    - sv_write_lines env/$k $v
  done
}

sv_mkdir() {
  (($+sv_name)) || die100 "$0: no service definition started"
  (($+sv_dir)) || die100 "$0: inconsistent state for service ${(qqq)sv_name}"
  (( $# < 1 )) && die100 "$0: incorrect arguments"

  - mkdir -p $sv_dir/${^@}
}

# end service builder

sv_common_finish() {
  local -a lines=(
    'backtick -E up { s6-svstat -o up ../'${(qqq)sv_name_server}' }'
    'backtick -E wantedup { s6-svstat -o wantedup ../'${(qqq)sv_name_server}' }'
    ## down this service if server is neither up nor wanted up
    'if -n { if { $up } $wantedup }'
    # 'ifelse { eltest true == $up -o true == $wantedup } { }'
    's6-svc -d .'
  )
  - sv_el_script finish $lines
}

write_sv_xorg() {
  - sv_start $sv_name_xorg
  - sv_notification_fd 3
  - sv_link_command run service.xpra-xorg.run
  - sv_common_finish
  - sv_env display_number $display
  - sv_end
}

write_sv_server() {
  - sv_start $sv_name_server
  - sv_el_script run \
    xpra-server $display --displayfd=3
  - sv_notification_fd 3
  - sv_end
}

write_sv_xclipring() {
  local selection=$1
  local name=xclipring-$selection.$display
  - sv_start $name
  - sv_el_script run \
    "if { mkdir -p /run/xclipring/$selection/$display }" \
    "in-xpra $display /usr/local/bin/xclipring" \
    "-s ${selection:u}" \
    "-c 32" \
    "-d /run/xclipring/$selection/$display"
  - sv_end
  client_services+=( $name )
}

write_sv_client() {
  - sv_start $sv_name_client
  - sv_el_script run \
    's6-envdir env' \
    xpra-client "$display" \
    "--title=\"[${container}] @title@ «s=@server-machine@ c=@client-machine@ d=$display w=@xid@\""
  - sv_common_finish
  - sv_env DISPLAY $DISPLAY
  - sv_end
  client_services+=( $sv_name_client )
}

write_sv_definitions() {
  - write_sv_xorg
  - write_sv_server
  - write_sv_client
  - write_sv_xclipring primary
  - write_sv_xclipring clipboard
}


do_unlock() {
  zsystem flock -u $lockfd
  unset lockfd
}

start_services() {
  - s6-svc -wU -T 5000 -o $scandir/$sv_name_server
  local s
  for s in $client_services; do
    - s6-svc -o $scandir/$s
  done
}

print_cookie() {
  local -a cookies
  # Obtain authentication cookie
  cookies=( $(xauth -f /run/Xauthority.$display list :$display ) )
  (( $#cookies == 3 )) || exit 1
  [[ $cookies[2] == MIT-MAGIC-COOKIE-1 ]] || exit 1
  printf '%s\t%s\n' $display $cookies[3]
}

main "$@"