#!/bin/bash -e

#
# SCRIPT CONSTANTS
#
DOCKER_DEFAULT_PORT=2376
DOCKER_DEFAULT_PROTOCOL="tcp"
GENERATORS_DIR=$(dirname "$0")/../system/docker/
GENERATE_COMPOSE_YML=$GENERATORS_DIR/generate-compose-yml
DEFAULT_MEM=4
DEFAULT_CU=1
DEFAULT_ELASTIC=0

#
# HELPER FUNCTIONS
#

# shellcheck source=../system/docker/aux_functions.sh
# shellcheck disable=SC1091
source "$GENERATORS_DIR/aux_functions.sh"

showHelp() {
  echo -e "
::::::::::::::: [  RUNCOMPSS-DOCKER  -  HELP  ] ::::::::::::::::::::::

First of all, remember that in order to use runcompss-docker you must have a working docker swarm,
and you need to have installed in this computer the docker engine.

Usage: $0
            --worker-containers=N
            --swarm-manager='<ip>:<port>'
            --image-name=\"DOCKERHUB_USER/IMG-NAME\"
			[rest of classic runcompss args]

Example: $0
            --worker-containers=5
            --image-name='compss-user-dockerhub/my-app:1.3'
            --swarm-manager='129.114.108.8:4000'
            --classpath=/home/compss-user/my-app-dir/my-app.jar # Here begin classic runcompss arguments...
            -d
            myPackage.MyApp 3 15


MANDATORY ARGUMENTS:

    --w, --worker-containers:  Specify the number of worker containers the app will execute on.
                               One more container will be created to host the master.
                               Example: --worker-containers=2

    --i, --image-name:         Specify the image name of the application image in Dockerhub. Remember you must generate this with runcompss-docker-gen-image.
                               Remember as well that the format must be: \"DOCKERHUB_USERNAME/APP_IMAGE_NAME:TAG\" (the :TAG is optional).
                               Example: --image-name='john123/my-compss-application:1.9'

    --s, --swarm-manager:      Specify the swarm manager ip and port (format:  <ip>:<port>).
                               Example: --swarm-manager='129.114.108.8:4000'

    --c, --context-dir:        Specify the absolute application context directory inside the image.
	            		       When using an application image, its provider must give you this information.
                               Example: --swarm-manager='129.114.108.8:4000'

OPTIONAL ARGUMENTS:

    --c-cpu-units:             Specify the number of cpu units used by each container (default value is $DEFAULT_CU).
                               Example: --c-cpu-units=16

    --c-memory:                Specify the physical memory used by each container in GB (default value is $DEFAULT_MEM GB).
                               Example: --c-memory=32  # (each container will use 32 GB)

    --c-creation-time:         Time required to create a docker container on cloud (default: 60 sec)
                               Example: --c-creation-time=12

    --c-elasticity:            Number of docker containers to run on cloud mode (default: $DEFAULT_ELASTIC)


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
"
}
get_master_hostname_ip(){
    master_hostname=$(docker stack ps ${IMG_NAME_WITHOUT_USERNAME}-docker | grep ${IMG_NAME_WITHOUT_USERNAME}-docker_master | awk '{ print $4 }' | xargs)
    master_ip=$(docker node inspect -f "{{print .Status.Addr}}" ${master_hostname})
}


get_master_status(){
    wait_before=$1
    if [ -n ${wait_before} ]; then
      sleep ${wait_before}
    fi 
    master_status=$(docker stack ps ${IMG_NAME_WITHOUT_USERNAME}-docker | grep ${IMG_NAME_WITHOUT_USERNAME}-docker_master | awk '{ print $5 }' | xargs)
}
retrieveResults() {
    cd "${TMP_DIR}/.."

    ECHO "Retrieving results from master..."

    # Copying results from master container to this machine... (docker cp)
    #IMG_NAME_WITHOUT_USERNAME="$(echo "$IMAGE_NAME" | sed -e 's|[A-Za-z0-9_]*/||g')"
    RESULTS_DIR="./${IMG_NAME_WITHOUT_USERNAME}-results"
    DEBUG_DIR="$RESULTS_DIR/debug"
    rm -rf "$RESULTS_DIR" &> /dev/null
    mkdir -p "$DEBUG_DIR"
    get_master_hostname_ip
    # We obtain Master container hash id by grepping the *_master container from a docker ps on the swarm manager.
    export DOCKER_HOST="${DOCKER_DEFAULT_PROTOCOL}://${master_ip}:${DOCKER_DEFAULT_PORT}"
 
    DOCKER_CONTAINER_MASTER_HASH=$(docker ps -a | grep "${IMG_NAME_WITHOUT_USERNAME}-docker_master" | awk '{ print $1 }')
    docker cp "${DOCKER_CONTAINER_MASTER_HASH}:${ABS_CONTEXT}" "${RESULTS_DIR}" ;
    ASSERT "Results could not be retrieved...Master container unreachable"
  
    docker cp "${DOCKER_CONTAINER_MASTER_HASH}:/root/.COMPSs"  "${DEBUG_DIR}" # &> /dev/null
    #ASSERT "Results could not be retrieved...Master container unreachable"
    
    docker logs ${DOCKER_CONTAINER_MASTER_HASH} 1> "${RESULTS_DIR}"/application_log.out 2>"${RESULTS_DIR}"/application_log.err
    # No .COMPSs hidden directory
    mv "${DEBUG_DIR}"/.COMPSs/* "${DEBUG_DIR}"
    rm -rf "${DEBUG_DIR}"/.COMPSs

    echo ; ECHO "Results successfully retrieved!" ; echo
    ECHO "Check the application results in '$RESULTS_DIR'"
    ECHO "In case you had debug enabled, check: '$DEBUG_DIR'"
    echo
}


#
# MAIN
#

ALL_ARGS=( "$@" )
COMPUTING_UNITS=${DEFAULT_CU}
MEMORY=${DEFAULT_MEM}
CREATION_TIME=60
MIN_VMS=0
MAX_VMS=${DEFAULT_ELASTIC}
RUNCOMPSS_ARGS=$* # this loop will strip from RUNCOMPSS_ARGS all the runcomps-docker args

# Retrieve parameters
for ARG in "${ALL_ARGS[@]}"; do
    argName="$(echo "$ARG" | cut -c 3- | cut -d= -f1)"
    argValue="$(echo "$ARG" | cut -d= -f2)"

    RD_GOOD_ARG=0
    if [ "$argName" = "worker-containers" ] || [ "$argName" = "w" ]; then
        if echo "$argValue" | grep -q -E "^[1-9][0-9]{0,}$"; then
            NUM_WORKER_CONTAINERS=$argValue
            RD_GOOD_ARG=1
        else
            ERROR "The --worker-containers argument must be a number >= 1. It's the number of worker containers that runcompss docker will spread across nodes (without taking into account the master container)."
        fi
    elif [ "$argName" = "image-name" ] || [ "$argName" = "i" ]; then
    	IMAGE_NAME="$argValue"
        RD_GOOD_ARG=1
    elif [ "$argName" = "swarm-manager" ] || [ "$argName" = "s" ]; then
        export DOCKER_HOST="$argValue" #<ip>:<port>
        SWARM_MANAGER_IP="$argValue"
        RD_GOOD_ARG=1
    elif [ "$argName" = "context-dir" ] || [ "$argName" = "c" ]; then
        ABS_CONTEXT="$argValue"
        RD_GOOD_ARG=1
    elif [ "$argName" = "c-cpu-units" ]; then
        COMPUTING_UNITS="$argValue"
        RD_GOOD_ARG=1
    elif [ "$argName" = "c-memory" ]; then
        MEMORY="$argValue"
        RD_GOOD_ARG=1
    elif [ "$argName" = "c-creation-time" ]; then
        CREATION_TIME="$argValue"
        RD_GOOD_ARG=1
    elif [ "$argName" = "c-elasticity" ]; then
        MAX_VMS="$argValue"
        RD_GOOD_ARG=1
    fi

    if [ "$RD_GOOD_ARG" = "1" ]; then
         # strip it from ALL_ARGS
        RUNCOMPSS_ARGS=$(echo "$RUNCOMPSS_ARGS" | sed "s|${ARG}||g")
    fi
done

# Log parameters
ECHO "Execution summary -----------------"
ECHO "Contatiner cpu units: $COMPUTING_UNITS"
ECHO "Container memory:     $MEMORY GB"
ECHO "Image name:           $IMAGE_NAME"
ECHO "Number of workers:    $NUM_WORKER_CONTAINERS"
ECHO "Swarm manager ip:     $SWARM_MANAGER_IP"
ECHO "Context directory:    $ABS_CONTEXT"
ECHO "Creation time:        $CREATION_TIME"
ECHO "Elastic Containers:   $MAX_VMS"
ECHO "-----------------------------------"
ECHO ""

# Check parameters
if [ -z "$1" ] || [ "$1" == "--help" ] || [ "$1" == "-h" ]
then
    showHelp
    exit 2
fi

ALL_GOOD=1
if [ -z "$NUM_WORKER_CONTAINERS" ]; then
    ERROR "Indicate the number of workers before runcompss args ('--worker-containers=3' for example)"
    ALL_GOOD=0
fi

if [ -z "$SWARM_MANAGER_IP" ]; then
    ERROR "Indicate the swarm manager <ip>:<port> before runcompss args ('--swarm-manager-name=129.116.120.45:4000' for example)"
    ALL_GOOD=0
fi

if [ -z "$IMAGE_NAME" ]; then
    ERROR "Indicate the image name. ('--image-name=compss-user/my-app-image' for example)."
    ALL_GOOD=0
fi

if [ -z "$ABS_CONTEXT" ]; then
    ERROR "Indicate the absolute context directory. Remember that the provider of this image must give you this information. ('--context-dir='/home/john123/apps/compss-app' for example)"
    ALL_GOOD=0
fi

if [ "$ALL_GOOD" = "0" ]; then
    echo
    ECHO "Run 'runcompss-docker --help' if you need help."
    echo
    exit 1
fi
IMG_NAME_WITHOUT_USERNAME="$(echo "$IMAGE_NAME" | sed -e 's|[A-Za-z0-9_]*/||g'| sed -e 's|[:.]|_|g')"
# Regular workflow
TMP_DIR="$PWD/${IMG_NAME_WITHOUT_USERNAME}-compose"
rm -rf "$TMP_DIR" &> /dev/null
mkdir "$TMP_DIR" &> /dev/null

ECHO "Generating docker-compose.yml file into '${TMP_DIR}' ..." ; echo
$GENERATE_COMPOSE_YML "$NUM_WORKER_CONTAINERS" "$IMAGE_NAME" "$ABS_CONTEXT" "$COMPUTING_UNITS" "$MEMORY" "$SWARM_MANAGER_IP" "$CREATION_TIME" "$MIN_VMS" "$MAX_VMS" "$RUNCOMPSS_ARGS" > "${TMP_DIR}"/docker-compose.yml
ASSERT "There was an error creating the docker compose yml file."

# Add trap for results retrieval
trap "retrieveResults ; exit 1" SIGINT ERR

ECHO "Cleaning environment from previous executions..." ; echo
# docker-compose down --remove-orphans
docker stack rm ${IMG_NAME_WITHOUT_USERNAME}-docker
# Sometimes, although the stack is removed, the previously stopped containers remain.
# The next command is to delete all previous containers.
RESIDUAL_CONTAINERS=$(docker ps -a | grep ${IMG_NAME_WITHOUT_USERNAME}-docker | awk '{ print $1 }' | xargs)
if [ ! -z "$RESIDUAL_CONTAINERS" ]; then
    docker rm -f $RESIDUAL_CONTAINERS > /dev/null
fi
#docker network runcompssdocker_runcompss-docker-net down
ASSERT "There was an error cleaning the environment from previous executions."
echo

ECHO "Waiting some seconds for the deletions to take effect"
sleep 5

ECHO "Executing application in swarm manager..." ; echo
# docker-compose up --force-recreate
docker stack deploy -c ${TMP_DIR}/docker-compose.yml ${IMG_NAME_WITHOUT_USERNAME}-docker
#docker-compose scale master=1 worker=$NUM_WORKER_CONTAINERS #--force-recreate
ASSERT "There was an error executing the application. Check the logs or the docker-compose output."
echo
ECHO "Waiting application to finish..."
get_master_status 5
while [[ "${master_status}" == "Running" ]]; do
	get_master_status 5
done
ECHO "Application finished!"
retrieveResults
exit 0

