#!/bin/bash

# BSD 3-Clause License
#
# Copyright (c) 2018, alessandrocomodi
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



# This script can be used by the application to start the host application built for sw or hw.
# It MUST be invoked from the application's build directory and should generally be invoked via 'make TARGET=xx launch'.
#
# TODO: This is a big hack. The web server should open the socket and start the host and tear down
#       the socket and host when it's done/killed. (Though consider how gdb can be run within editor where the editor must execute the host command.)
#
# This should be run via the Makefile.

# Usage:
#   ./launch [-c <compile-command>] [-w <web-server-command>] [-p <port>] <hw/sw> [<host-command>]
# Usage Cases (without optional args):
#   ./launch hw '<path/to/host> <path/to/awsxclbin>'
#   ./launch hw_emu '<path/to/host> <path/to/xclbin>'
#   ./launch sw   # <host-command> defaults to "../out/$TARGET/host" for sw target.
# Debug Usage Cases (without optional args):
#   ../out/sw/host &  # from within debugger
#   ps aux | grep ../out/sw/host
#   launch -h <pid> sw '<host-command>'   # OR
#   launch -h none sw '<host-command>'    # to not kill host on exit ("none" is anything that isn't a whole number)
# Optional args:
#   -p <port>: The port number on which to launch. 8888 by default.
#   -c <compile-command>: The compile command used to rebuilt when upgraded.
#   -w <web-server-command>: The command to launch the web server.
#             '<<PORT>>' will be substituted with the port number;
#             '<<SOCKET>>' will be substituted with the socket number;
#             '<<PASSWORD>>' will be substituted with $LAUNCH_PASSWORD;
#   -k <killme-file>: The "killme" file to create (which, when source'd will kill the microservice).
#   -s <socket-file>: The file to use for the socket.
#   -o: Launch webserver in one-shot mode, where it will kill the launch process when the websocket is closed (browser tab is closed).

# Must be launched from the projects 'build' directory because:
#   o If -w is not specified or the project utilizes default routing, the default web content paths are "../webserver/*".

# TODO: Make a function to wrap commands in sudo.

usage () {
    echo "Usage: launch [-p #] [-h host-pid] [-c compile-command] [-w web-server-args] (sw|hw_emu|hw) host-command"
    exit 1
}


# Defaults

SCRIPT_CMD="launch $*"
PORT="8888"
HOST_PID=""
COMPILE_CMD=""
LAUNCH_WEBSERVER_CMD='python3 ../../../framework/webserver/default_server.py --port=<<PORT>> --socket=<<SOCKET>>'
KILLME="killme"
SOCKET="SOCKET"
ONESHOT_ARG=' '

# Read command line options

while getopts "p:h:c:w:k:s:o" opt
  do
    case "${opt}" in
      p) PORT=${OPTARG};;
      h) HOST_PID=${OPTARG};;
      c) COMPILE_CMD="${OPTARG}";;
      w) LAUNCH_WEBSERVER_CMD="${OPTARG}";;
      k) KILLME="${OPTARG}";;
      s) SOCKET="${OPTARG}";;
      o) ONESHOT_ARG=" --oneshot=$$"
    esac
  done
shift $((OPTIND -1))
LAUNCH_WEBSERVER_CMD="$LAUNCH_WEBSERVER_CMD$ONESHOT_ARG"

TARGET="$1"
HOST="$2"

if [[ $HOST_PID =~ ^[0-9]+$ ]];
then
  HOST_PID=""
fi

if [[ $TARGET = "" ]];
then
  usage
fi
if [[ $TARGET = "hw" ]];
then
    if [[ $HOST = "" ]];
    then
      usage
    fi
fi

if [[ $TARGET = "sw" || $TARGET = "sim" ]]
then
  USE_XILINX=false
else
  USE_XILINX=true
fi

if [[ $TARGET = "hw" ]]
then
  USE_FPGA=true
else
  USE_FPGA=false
fi

if [[ $USE_XILINX = "false" ]] && [[ $HOST = "" ]];
then
  HOST="../out/$TARGET/host"
fi

if [[ $COMPILE_CMD = "" ]];
then
  COMPILE_CMD="make TARGET=$TARGET build"
fi

fail () {
    echo "launch: $1"
    exit 1
}


# Launch web server and host application.
launch () {
  # Launch host app.
  echo "Launching host application as: $HOST"
  if [[ $HOST_PID == "" ]]
    # Host application launched externally (probably via gdb).
  then
    if [[ $USE_XILINX = "false" ]] && [[ $USE_FPGA = "false" ]]; #Simulation
    then
        $HOST &
    elif [[ $USE_XILINX = "true" ]] && [[ $USE_FPGA = "false" ]]; # Hardware Emulation
    then
      sudo -- sh -c "source $XILINX_VITIS/settings64.sh; source /opt/xilinx/xrt/setup.sh; source $AWS_FPGA_REPO_DIR/vitis_setup.sh; emconfigutil --nd 1  --platform $AWS_PLATFORM; export XCL_EMULATION_MODE=hw_emu ; export LC_ALL="C"; $HOST" &
    else # Hardware
      sudo -- sh -c "source $XILINX_VITIS/settings64.sh; source /opt/xilinx/xrt/setup.sh; source $AWS_FPGA_REPO_DIR/vitis_setup.sh; source $AWS_FPGA_REPO_DIR/vitis_runtime_setup.sh; export LANG=C; $HOST" &
    fi
    export HOST_PID=$!
  fi

  # Make sure we have tornado. (Too slow.)
  #pip -q install tornado


  # Launch web server.

  # Substitute <<PORT>>.
  CMD=${LAUNCH_WEBSERVER_CMD/<<PORT>>/$PORT}
  CMD=${CMD/<<SOCKET>>/$SOCKET}
  # Launch.
  echo "Launching web server as: $CMD"
  # Substitute <<PASSWORD>>.
  CMD=${CMD/<<PASSWORD>>/$LAUNCH_PASSWORD}
  if [[ $USE_XILINX = "false" ]];
  then
    $CMD &
  else
    sudo $CMD &
  fi
  export SERVER_PID=$!

  # Create a script to teardown the application by sending SIGINT to this process.
  echo "kill $$ || echo 'Webserver process $$ no longer running.'" > "$KILLME"   # TODO: Would be good to confirm that this is the right process.
}

# Tear down.
teardown () {
  # Kill child processes
  echo "launch: Killing web server and host application."
  if [[ $USE_XILINX = "false" ]];
  then
    if [[ $HOST_PID != "" ]]
    then
      kill $HOST_PID
    fi
    kill $SERVER_PID
    rm "$SOCKET"   # Needed? Doesn't even work.
  else
    # Processes are invoked through sudo and bash and don't seem to be killed with their parent.
    HOST_PID2=`ps --ppid $HOST_PID -o pid=`
    HOST_PID3=`ps --ppid $HOST_PID2 -o pid=`
    SERVER_PID2=`ps --ppid $SERVER_PID -o pid=`
    echo "launch: Killing PIDs: ($HOST_PID>$HOST_PID2>)$HOST_PID3 ($SERVER_PID>)$SERVER_PID2"
    ps --pid $HOST_PID --pid $HOST_PID2 --pid $HOST_PID3 --pid $SERVER_PID --pid $SERVER_PID2
    #sudo kill -9 $HOST_PID2
    sudo kill -9 $HOST_PID3
    sudo kill -9 $SERVER_PID2
  fi
  
  # Done with "killme" script, created by this script.
  # This script might currently be in use, but it seems to be okay to delete it?
  rm "$KILLME"
  
  # Wait for child processes to finish first (to avoid output after exit)
  wait $HOST_PID >& /dev/null
  wait $SERVER_PID >& /dev/null
}

# Kill signal handler.
finish () {
  echo "launch: Tearing down."
  teardown
  echo "launch: Killed host app and web server"
  DONE=1
}


# SIGUSR1 signal invokes this to pull the latest repository and re-launch.
upgrade () {
  echo "launch: Upgrade"
  # Return if there are no changes in master.
  git pull origin master
  OUT=`git status`
  if ! [[ $OUT =~ Your\ branch\ is\ behind ]] ; then
    echo "launch: Up to date with master."
  else
    echo "launch: Pulled changes. Updating submodules."
    git submodule update --init --recursive

    echo "launch: Re-launching web server."
    # Kill children.
    teardown

    # Compile
    bash -c "$COMPILE_CMD" || fail "Compilation for Mandelbrot failed."

    # Re-launch.
    echo "launch: Re-launching web server"
    exec $SCRIPT_CMD
  fi
}

launch

trap finish SIGINT SIGQUIT SIGABRT SIGKILL SIGPIPE SIGTERM
trap upgrade SIGUSR1

DONE=0
while [ $DONE -le 0 ] ; do
  sleep 2
done

echo "launch: Done."
