#!/bin/bash

function LockStartup()
{
  InitDirs			|| return 0 # only try locking
  cmdExists "flock" || return 0 # if dir and cmd exist

  exec 300>> "$TV_STARTLOG" || die "LockStartup: internal error $?"
  flock -n 300 || die "LockStartup: TeamViewer already running $?"
}

function UnlockStartup()
{
  flock -u -n 300
  exec 300<&-
}

function RequireNetwork()
{
  IsDaemonRunning && return

  echo "Starting network process (no daemon)"

  RunNetworkProcess
}

function RequireWineServer()
{
  # Automatic start of wineserver fails sometimes when spawning from daemon, thus make sure it is running beforehand.
  # For QS, wineserver must run before profile creation (or be patched)
  (
    UnlockStartup	# wineserver shall not inherit the lock
    exec "$TV_BIN_DIR/wine/bin/wineserver"
  )
}

# Delay until Xrandr changed the screen configuration. Wine does not like it if the screen configuration changes.
# e.g. If the screen "grows", The windows will still be confined to the old area, the tray icon may not work
# NOTE: To enable this, create the file "echo 15 > /opt/teamviewer/config/waitxrandr"
# where 15 is the amount of seconds to wait at most.
function XRandRWait()
{
  local xrandrConf="$TV_BASE_DIR/config/waitxrandr"
  local xrandrDelay
  local orgConf
  local chgConf
  local secs=0

#  [ -f "$xrandrConf" ] || return  # now using a default delay
  cmdExists xrandr     || return

  xrandrDelay=$(cat "$xrandrConf" 2> /dev/null)
  if ! (isStrNumeric "$xrandrDelay" && (( xrandrDelay >= 0  && xrandrDelay <= 100 ))); then
    echo "XRandRWait: No value set. Using default."
    xrandrDelay=3
  fi

  if [ "$(ps -p $PPID -o comm=)" != 'teamviewerd' ]; then
    echo "XRandRWait: Started by user."; return
  fi

  orgConf=$(XRandRCurConf)

  until [ $secs = $xrandrDelay ]; do
    chgConf=$(XRandRCurConf)

    if [ "$orgConf" != "$chgConf" ]; then
      break
    fi

    sleep 1
    let secs+=1
  done

  echo "Waited $secs seconds (of $xrandrDelay) for xrandr"
}

function XRandRCurConf()
{
  xrandr --current | tr '\n' '_'
}

function IsDaemonRunning()
{
  (
    #; Check if daemon is running - ignore for non-installed (TAR / TAR_QS)
    isInstalledTV || return 1

    exec &> /dev/null
    ps --no-heading -p $(cat "$TV_PIDFILE") | grep teamviewerd
  )
}

function RunNetworkProcess()
{
  local subs
  local subPID
  local repeat=20

  # Start a network process
  trap Network_Signal SIGUSR1

  "$TV_BIN_DIR/teamviewerd" -n -f &
  subPID=$!

  # wait works, but could be entered too late
  until [ $repeat = 0 ]; do
    subs=$(jobs -r | wc -l)		# or: while subPID running

    if [ $subs = 0 ]; then		# network process quit (error or already running)
      echo "Network process already started (or error)"; break
    fi
    if [ -n "$TV_NET_STATE" ]; then	# signalled
      echo "Network process started ($subPID)"; break
    fi

    sleep 0.5
    let repeat-=1
  done
}

function Network_Signal()
{
  TV_NET_STATE='is_up'
}

function PatchPatchELF()
{
 ( # subshell for cd
  cd "$TV_RTLIB_DIR"

  if ! [ -e patchelf.org ]; then
    cp 'patchelf' 'patchelf.org' || die 'Copy patchelf failed'
  fi

  local rpath=$(LD_LIBRARY_PATH="$TV_RTLIB_DIR" "$TV_BINLOADER" "./patchelf.org" --print-rpath 'patchelf') || die 'PatchPatchElf: failed 1'
  local intrp=$(LD_LIBRARY_PATH="$TV_RTLIB_DIR" "$TV_BINLOADER" "./patchelf.org" --print-interpreter 'patchelf') || die 'PatchPatchElf: failed 2'

  if [ "$rpath" = "$TV_RTLIB_DIR" ] && [ "$intrp" = "$TV_BINLOADER" ]; then
    echo 'PatchElf: ok'
  else
    LD_LIBRARY_PATH="$TV_RTLIB_DIR" "$TV_BINLOADER" "./patchelf.org" --set-rpath "$TV_RTLIB_DIR" 'patchelf'       || die 'PatchPatchElf: failed 3'
    LD_LIBRARY_PATH="$TV_RTLIB_DIR" "$TV_BINLOADER" "./patchelf.org" --set-interpreter "$TV_BINLOADER" 'patchelf' || die 'PatchPatchElf: failed 4'
    echo 'PatchElf: patched'
  fi
 ) || exit 1

  PATCHELF="$TV_RTLIB_DIR/patchelf"
}

# patch binaries to use shipped libraries/loader (patchelf)
function UpdateBinaries()
{
  isQuickSupport || return 0

  echo 'Updating binaries...'

  local bmark="$TV_CFG_DIR/rtldir"
  local bpath=$(cat "$bmark" 2> /dev/null)
  local ublog=$(UpdateBinaryLogfile)

  if [ "$bpath" = "$TV_RTLIB_DIR" ]; then
    echo "Already up to date"
    return 0
  fi

  ( # subshell for redirecting output
    exec >> "$ublog" 2>&1

    #prepare patcher
    PatchPatchELF
    
    # extract xz binaries
    ExtractBinaries    

    # bash expansion: after ExtractBinaries
  local binaries=(
		"$TV_BIN_DIR/teamviewerd"
		"$TV_BIN_DIR/TeamViewer_Desktop"
		"$TV_BIN_DIR/TVGuiSlave.32"
		"$TV_BIN_DIR/wine/bin/wine"
		"$TV_BIN_DIR/wine/bin/wineserver"
		"$TV_BIN_DIR/wine/drive_c/TeamViewer/tvwine.dll.so"
		"$TV_BIN_DIR/wine/bin/wine"
		"$TV_BIN_DIR/wine/lib/libwine.so.1.0"
		"$TV_BIN_DIR"/wine/lib/wine/*.*.so
		"$TV_RTLIB_DIR"/lib*.so*
		)

    # update each binary
    for bin in "${binaries[@]}"; do
      [ -h "$bin" ] && continue
      UpdateBinary "$bin"
    done

    PrintBinaryPaths "${binaries[@]}"
  ) || return

  echo "$TV_RTLIB_DIR" > "$bmark" || return 1
  echo "Binaries patched"
}

function ExtractBinaries()
{
  local xzArchive='archive.tar.xz'
  local arcPath="$TV_BIN_DIR/$xzArchive"
  local testFile="$TV_BIN_DIR/teamviewerd"

  if [ -f "$arcPath" ]; then
    UpdateXZBinaries
    ExtractXZBinaries
  fi
  
  [ -f "$testFile" ] || die "  XZ: Missing file '$testFile'"
  
  echo '  XZ: ok'
}

function UpdateXZBinaries()
{
  local binaries=(
		"$TV_RTLIB_DIR/xzdec"
		"$TV_RTLIB_DIR/libc.so.6"
		"$TV_RTLIB_DIR/libgcc_s.so.1"
		)

	local rbin

    echo '  XZ: updating...'
    for bin in "${binaries[@]}"; do
      rbin=$(readlink -e "$bin")		# resolve symlinks
      [ -f "$rbin" ] || die "  XZ: missing '$bin' ($rbin)"
      UpdateBinary "$rbin"
    done
}

function ExtractXZBinaries()
{
    echo '  XZ: extracting'
    cmdExists tar || die "Missing 'tar' command"

    ( # subshell
      cd "$TV_BIN_DIR"
      RTlib/xzdec $xzArchive | tar x || die "XZ: unxz failed ($?)"
      #RTlib/unxz archive.tar.xz || die "XZ: unxz failed ($?)"
      rm "$xzArchive"
    )
}

function UpdateBinaryLogfile()
{
  local count=1
  local patrn="$TV_LOG_DIR/BinaryPatch%03i.log"
  local lfile
  
  while : ; do
    lfile=$(printf "$patrn" $count)
    [ -e "$lfile" ] || break;
    (( count++ ))
  done
  
  echo "$lfile"
}

function UpdateBinary()
{
  local bin="$1"
  local name=${bin##/*/}

  echo "Patching $name ..."
  PatchIntrp "$bin"
  PatchRPath "$bin"
}

function PatchRPath()
{
  local file="$1"
  local path="$TV_RTLIB_DIR"
  local name=${file##/*/}
  local orig=$("$PATCHELF" --print-rpath "$file" 2> /dev/null)

  if [[ "$orig" = *ORIGIN* ]] ; then
    path="$path:\${ORIGIN}/../lib"
  fi

  [ "$name" = tvwine.dll.so ] && path="$path:$TV_EXTENSION"

  [ "$orig" = "$path" ] && return

  printf "  orig: %s\n  path: %s\n" "$orig" "$path"

  "$PATCHELF" --set-rpath "$path" "$file" || die "PatchRPath $name failed"
}

function PatchIntrp()
{
  local file="$1"
  local path="$TV_BINLOADER"
  local name=${file##/*/}
  local orig=$("$PATCHELF" --print-interpreter "$file" 2> /dev/null)

  [ -z "$orig" ]        && return	# interpreter n/a
  [ "$orig" = "$path" ] && return	# interpreter ok

  "$PATCHELF" --set-interpreter "$path" "$file" || die "PatchInterp $name failed"
}

function PrintBinaryPaths()
{
  local binaries=("$@")

  printf '\nBinary paths:\n'

  for bin in "${binaries[@]}"; do
    [ -h "$bin" ] && continue
    PrintBinaryPath "$bin" || die 'PrintBinaryPath failed'
  done

  echo
}

function PrintBinaryPath()
{
  local bin="$1"
  local name=${bin##/*/}
  local rpath=$("$PATCHELF" --print-rpath "$bin")
  local intrp=$("$PATCHELF" --print-interpreter "$bin" 2> /dev/null)

  printf "%22s - %20s - %s\n" "$name" "$rpath" "$intrp"
}
