#!/bin/bash

BDIR=$(cd $(dirname $0)/.. && pwd)

#configuration
declare -A CONFIG
CONFIG[PORT]=30080
CONFIG[INSTMETHOD]=global
CONFIG[PREFIX]=""
CONFIG[UPGRADE]=""
CONFIG[CERTFILE]=""
CONFIG[KEYFILE]=""
CONFIG[CMD]=$(basename $0)
CONFIG[MODE]="simple"
CONFIG[GETTASK]="false"
CONFIG[ANONSUBMIT]="false"
CONFIG[MOSEKDIR]=""
CONFIG[MOSEKBINDIR]=""
CONFIG[DBRES]=""
CONFIG[BINCHECK]="true"
CONFIG[TLS]="false"

SETLOC=""

CMD=$0

function usage() {
          ################################################################################
    echo "Syntax: $CMD OPTIONS"
    echo "OPTIONS:"
    echo "  --upgrade                Update server files, but do not overwrite"
    echo "                           configuration and SSL certificates."
    echo "  --mosekdir               Defines the location of the MOSEK installation"
    echo "  --mosekbindir            Defines the the MOSEK binaries"

    echo "  --inplace                Set up the server to run directly in the unpacked"
    echo "                           distro directory."
    echo "  --user                   Install in ~/.local"
    echo "  --global                 Install in /usr"
    echo "  --prefix DIR             Install in DIR"

    echo "  --database-resource STR  Set the Postgres database connection string."

    echo "  --port NUM               Serve on port NUM"
    echo "  --mode (simple|api|gui)  Enable only the simple API, enable users and user"
    echo "                           API, or enable user API and GUI. For non-ssl mode"
    echo "                           only 'simple' is available."
    echo "  --ssl DIR                This directory contains valid cert.pem and key.pem"
    echo "                           for HTTPS. DIR='', HTTPS is disabled"
    echo "  --server-key FILE        The server key file"
    echo "  --server-cert FILE       The server certificate file"
    echo "  --enable-get             Enable fetching submitted data/problem files"
    echo "  --enable-anonymous       Enable submitting without credentials"
}

while (($# > 0)); do
    case "$1" in
        "--inplace")
            if [ -n "$SETLOC" ]; then
                echo "Fail: Only one if --inplace, --user, --global and --prefix can be used"
                exit 1
            fi
            CONFIG[INSTMETHOD]="inplace"
            CONFIG[PREFIX]="$BDIR"
            BINDIR="${CONFIG[PREFIX]}/bin"
            VARDIR="${CONFIG[PREFIX]}/var"
            ETCDIR="${CONFIG[PREFIX]}/etc"
            SETLOC="YES"
            shift
            ;;
        "--user")
            if [ -n "$SETLOC" ]; then
                echo "Fail: Only one if --inplace, --user, --global and --prefix can be used"
                exit 1
            fi
            CONFIG[INSTMETHOD]="user"
            CONFIG[PREFIX]=$HOME/.local
            BINDIR="${CONFIG[PREFIX]}/bin"
            VARDIR="${CONFIG[PREFIX]}/var"
            ETCDIR="${CONFIG[PREFIX]}/etc"
            SETLOC="YES"
            shift
            ;;
        "--global")
            if [ -n "$SETLOC" ]; then
                echo "Fail: Only one if --inplace, --user, --global and --prefix can be used"
                exit 1
            fi
            CONFIG[INSTMETHOD]="global"
            CONFIG[PREFIX]="/usr"
            BINDIR="${CONFIG[PREFIX]}/bin"
            VARDIR="/var"
            ETCDIR="/etc"
            SETLOC="YES"
            shift
            ;;
        "--prefix")
            if [ -n "$SETLOC" ]; then
                echo "Fail: Only one if --inplace, --user, --global and --prefix can be used"
                exit 1
            fi
            CONFIG[INSTMETHOD]="prefix"
            mkdir -p "$2"
            pushd "$2"
            CONFIG[PREFIX]=$(pwd)
            popd
            BINDIR="${CONFIG[PREFIX]}/bin"
            VARDIR="${CONFIG[PREFIX]}/var"
            ETCDIR="${CONFIG[PREFIX]}/etc"
            SETLOC="YES"
            shift 2
            ;;
        "--upgrade")
            CONFIG[UPGRADE]="yes"
            shift
            ;;
        "--ssl")
            if [ "$2" == "" ]; then
                echo "Error: Invalid SSL dir $2"
                exit 1
            elif [ -d "$2" ]; then
                SSLDIR=$(cd "$2"; pwd)
                CONFIG[CERTFILE]=$SSLDIR/cert.pem
                CONFIG[KEYFILE]=$SSLDIR/key.pem
            else
                echo "Error: Invalid SSL dir $2"
                exit 1
            fi
            shift 2
            ;;
        "--server-key")
            if [ "$2" == "" ]; then
                echo "Error: Invalid Server key $2"
                exit 1
            elif [ -d $(dirname "$2") ]; then
                CONFIG[KEYFILE]=$( cd $(dirname "$2") && echo $(pwd)/$(basename "$2"))
            else
                echo "Error: Invalid Server key $2"
                exit 1
            fi
            shift 2
            ;;
        "--server-cert")
            if [ "$2" == "" ]; then
                echo "Error: Invalid Server certificate $2"
                exit 1
            elif [ -d $(dirname "$2") ]; then
                CONFIG[CERTFILE]=$( cd $(dirname "$2") && echo $(pwd)/$(basename "$2"))
            else
                echo "Error: Invalid Server key $2"
                exit 1
            fi
            shift 2
            ;;
        "--mosekdir")
            if [ "$2" == "" ]; then
                echo "Error: Directory argument for --mosekdir required"
                exit 1
            else
                CONFIG[MOSEKDIR]=$(cd "$2" && pwd )
                echo "MOSEKDIR = ${CONFIG[MOSEKDIR]}"
            fi
            shift 2
            ;;
        "--mosekbindir")
            if [ "$2" == "" ]; then
                CONFIG[MOSEKBINDIR]=""
            else
                mkdir -p "$2"
                CONFIG[MOSEKBINDIR]=$(cd $2 && pwd)
            fi
            shift 2
            ;;
        "--port")
            CONFIG[PORT]=$2
            shift 2
            ;;
        "--database-resource")
            CONFIG[DBRES]="$2"
            shift 2
            ;;
        "--mode")
            case "$2" in
                "simple") CONFIG[MODE]="simple" ;;
                "api")    CONFIG[MODE]="api" ;;
                "gui")    CONFIG[MODE]="gui" ;;
                "*")
                    echo "Error: Invalid mode $2"
                    ;;
            esac
            shift 2
            ;;
        "--enable-anonymous")
            CONFIG[ANONSUBMIT]="true"
            shift
            ;;
        "--enable-get")
            CONFIG[GETTASK]="true"
            shift
            ;;
        "--no-bincheck")
            CONFIG[BINCHECK]="false"
            shift
            ;;
        "--help"|"-h")
            usage
            exit 1
            ;;
        "-*")
            echo "Invalid flag: $1"
            exit 1
            ;;
        *)
            echo "Invalid argument: $1"
            exit 1
            ;;
    esac
done

if [ "${CONFIG[PREFIX]}" == "" ]; then
    usage
    exit 1
fi

if [[ "${CONFIG[KEYFILE]}" != "" && "${CONFIG[CERTFILE]}" != "" ]]; then
    CONFIG[TLS]="true"
fi

if [[ "${CONFIG[MODE]}" != "simple" ]]; then
    if [[ "${CONFIG[TLS]}" != "true" ]]; then
        echo "ERROR! Mode=${CONFIG[MODE]} without TLS not supported. Enable TLS."
    fi
fi

function abspath() { cd $1 ; pwd; }

function locateMosekBinDir() {
    MOSEKDIR=$1

    if [ -z "$MOSEKDIR" ]; then
        MOSEKDIR=$HOME/mosek
    fi

    if [ -d "$MOSEKDIR" ]; then
        #VER=$(ls "$MOSEKDIR" | grep '^[0-9]\+\(\.[0-9]\+\)\?\>$' | sort -r | head -n 1)
        #echo $(ls "$MOSEKDIR" | grep '^[0-9]\+\(\.[0-9]\+\)\?\>$' | sort -n -r )
        for VER in $(ls "$MOSEKDIR" | grep '^[0-9]\+\(\.[0-9]\+\)\?\>$' | sort -n -r ); do
            if [[ -d "$MOSEKDIR/$VER" && -d "$MOSEKDIR/$VER/tools/platform/linux64x86/bin" ]]; then
                echo "$MOSEKDIR/$VER/tools/platform/linux64x86/bin"
                exit 0
            fi
        done

        exit 1
    else
        exit 1
    fi
}

if [ -z "${CONFIG[MOSEKBINDIR]}" ]; then
    CONFIG[MOSEKBINDIR]="$(locateMosekBinDir ${CONFIG[MOSEKDIR]})"
    if [ -z "${CONFIG[MOSEKBINDIR]}" ]; then
        echo "Failed to locate a suitable MOSEK version" && exit 1
    fi
fi

if [ "${CONFIG[DBRES]}" == "" ]; then
    CONFIG[DBRES]="user=$USER dbname=$USER password=$USER sslmode=disable"
fi

if [ -d "${CONFIG[MOSEKBINDIR]}" ]; then
    MOSEKBINDIR=$(abspath "${CONFIG[MOSEKBINDIR]}")

    if [ -e "$MOSEKBINDIR/mosekcli" ]; then
        SOLVECMD="$MOSEKBINDIR/mosekcli -pid-file \$workdir/PID -log \$workdir/solver.log -read \$taskfile -solve:res=\$workdir/result.res,trm=\$workdir/result.trm,msg=\$workdir.msg -write-solution:json \$workdir/solution.jtask -write-solution:task \$workdir/solution.task -write-solution:plain \$workdir/solution.ascii -indicator-file:done \$workdir/done"
    elif [ "${CONFIG[BINCHECK]}" == "false" ]; then
        SOLVECMD="$MOSEKBINDIR/mosekcli -pid-file \$workdir/PID -log \$workdir/solver.log -read \$taskfile -solve:res=\$workdir/result.res,trm=\$workdir/result.trm,msg=\$workdir.msg -write-solution:json \$workdir/solution.jtask -write-solution:task \$workdir/solution.task -write-solution:plain \$workdir/solution.ascii -indicator-file:done \$workdir/done"
    else
        echo "Missing 'mosekcli' command in ${CONFIG[MOSEKBINDIR]}" && exit 1
    fi
else
    echo "${CONFIG[MOSEKBINDIR]} is not a directory" && exit 1
fi

CONFIGFILE="$ETCDIR/Mosek/server.conf"

mkdir -p "$VARDIR/Mosek/HTML/web/datatables" && \
unzip -o "$BDIR/var/Mosek/DataTables.zip" -d "$VARDIR/Mosek/HTML/web/datatables" && \
if [ "${CONFIG[INSTMETHOD]}" != "inplace" ]; then
    echo "Make directories..."                        && \
    mkdir -p "$BINDIR"                                && \
    mkdir -p "$ETCDIR/Mosek/ssl"                      && \
    mkdir -p "$VARDIR/Mosek/HTML/web/script"          && \
    mkdir -p "$VARDIR/Mosek/HTML/web/style"           && \
    mkdir -p "$VARDIR/Mosek/HTML/web/image"           && \
    mkdir -p "$VARDIR/Mosek/HTML/web/datatables"      && \
    mkdir -p "$VARDIR/Mosek/HTML/content"             && \
    mkdir -p "$VARDIR/Mosek/HTML/template"            && \
    mkdir -p "$VARDIR/Mosek/HTML/default"             && \
    mkdir -p "$VARDIR/Mosek/jobs/tasks"               && \
    \
    echo "Install into ${CONFIG[PREFIX]}" && \
    cp "$BDIR/bin/MosekServer.bin" \
       "$BDIR/bin/MosekServer" \
       "$BINDIR/" &&   \
    \
    echo "  Javascript... " && \
    cp  "$BDIR/var/Mosek/HTML/web/script/optapi.mjs" \
        "$BDIR/var/Mosek/HTML/web/script/request.mjs" \
        "$BDIR/var/Mosek/HTML/web/script/gui.mjs"    \
        "$BDIR/var/Mosek/HTML/web/script/utils.mjs"  \
        "$BDIR/var/Mosek/HTML/web/script/jobs.mjs"   \
        "$BDIR/var/Mosek/HTML/web/script/tokens.mjs" \
        "$BDIR/var/Mosek/HTML/web/script/users.mjs"  \
        "$BDIR/var/Mosek/HTML/web/script/stats.mjs"  \
        "$BDIR/var/Mosek/HTML/web/script/profile.mjs"  \
        "$BDIR/var/Mosek/HTML/web/script/login.mjs"  \
        "$BDIR/var/Mosek/HTML/web/script/jquery-3.6.0.min.js"  \
        "$VARDIR/Mosek/HTML/web/script" && \
    \
    echo "  Stylesheets... " && \
    cp  "$BDIR/var/Mosek/HTML/web/style/style.css" \
        "$BDIR/var/Mosek/HTML/web/style/stub.css" \
        "$BDIR/var/Mosek/HTML/web/style/jobs.css" \
        "$VARDIR/Mosek/HTML/web/style" && \
    \
    echo "  Graphics... " && \
    cp  "$BDIR/var/Mosek/HTML/web/image/favicon.png" \
        "$BDIR/var/Mosek/HTML/web/image/mosekserver.png" \
        "$BDIR/var/Mosek/HTML/web/image/webgraphmoseklogocolor.png" \
        "$BDIR/var/Mosek/HTML/web/image/btn-stop.png" \
        "$BDIR/var/Mosek/HTML/web/image/btn-run.png" \
        "$BDIR/var/Mosek/HTML/web/image/btn-delete.png" \
        "$BDIR/var/Mosek/HTML/web/image/btn-info.png" \
        "$VARDIR/Mosek/HTML/web/image" && \
        \
    echo "  Web content... " && \
    cp  "$BDIR/var/Mosek/HTML/web/login.html" \
        "$VARDIR/Mosek/HTML/web" && \
    \
    cp  "$BDIR/var/Mosek/HTML/content/mytokens.html"  \
        "$BDIR/var/Mosek/HTML/content/myjobs.html"    \
        "$BDIR/var/Mosek/HTML/content/alltokens.html" \
        "$BDIR/var/Mosek/HTML/content/alljobs.html"   \
        "$BDIR/var/Mosek/HTML/content/index.html"     \
        "$BDIR/var/Mosek/HTML/content/profile.html"   \
        "$BDIR/var/Mosek/HTML/content/userlist.html"  \
        "$BDIR/var/Mosek/HTML/content/stats.html"  \
        \
        "$VARDIR/Mosek/HTML/content" && \
    \
    cp  "$BDIR/var/Mosek/HTML/template/default.html"  \
        \
        "$VARDIR/Mosek/HTML/template" && \
    \
    cp  "$BDIR/var/Mosek/HTML/default/favicon.png" \
        "$BDIR/var/Mosek/HTML/default/webgraphmoseklogocolor.png" \
        "$BDIR/var/Mosek/HTML/default/index.html" \
        "$BDIR/var/Mosek/HTML/default/script.js" \
        "$BDIR/var/Mosek/HTML/default/optapi.mjs" \
        "$VARDIR/Mosek/HTML/default" && \
    \
    if [ -z "${CONFIG[UPGRADE]}" ]; then
       if [ "${CONFIG[CERTFILE]}" != "" ]; then
           mkdir -p "$ETCDIR/Mosek/ssl" && \
               rm -f "$ETCDIR/Mosek/ssl/cert.pem" && \
               ln -s "${CONFIG[CERTFILE]}" "$ETCDIR/Mosek/ssl/cert.pem" && \
               chmod go-rw "$ETCDIR/Mosek/ssl/cert.pem"
       fi && \
       if [ "${CONFIG[KEYFILE]}" != "" ]; then
           mkdir -p "$ETCDIR/Mosek/ssl" && \
               rm -f "$ETCDIR/Mosek/ssl/key.pem" && \
               ln -s "${CONFIG[KEYFILE]}"  "$ETCDIR/Mosek/ssl/key.pem" && \
               chmod go-rw "$ETCDIR/Mosek/ssl/key.pem"
       fi && \
       echo "Creating server config file" && \
           
       echo "#!/bin/bash" > "$BINDIR/MosekServer" && \
       echo "exec \"$BINDIR/MosekServer.bin\" -config=\"$CONFIGFILE\" \"\$@\"" >> "$BINDIR/MosekServer" && \
       chmod +x "$BINDIR/MosekServer"
    fi
else # inplace
    if [ -z "${CONFIG[UPGRADE]}" ]; then
       if [ "${CONFIG[CERTFILE]}" != "" ]; then
           chmod go-rw "$ETCDIR/Mosek/ssl/cert.pem"
       fi && \
       if [ "${CONFIG[KEYFILE]}" != "" ]; then
           chmod go-rw "$ETCDIR/Mosek/ssl/key.pem"
       fi && \
       mkdir -p "$VARDIR/Mosek/jobs/tasks" && \

       echo "#!/bin/bash" > "$BINDIR/MosekServer" && \
       echo "exec \"$BINDIR/MosekServer.bin\" -config=\"$CONFIGFILE\" \"\$@\"" >> "$BINDIR/MosekServer" && \
       chmod +x "$BINDIR/MosekServer"
    fi 
fi && \
\
echo "Creating server configuration ($CONFIGFILE)"  && \
\
if [[ ! -f "$CONFIGFILE" || -z "${CONFIG[UPGRADE]}" ]]; then
    ( echo "[network]"
      echo "# Host:Port tells the server under what name it should accept connections "
      echo 'Host          = '
      echo "Port          = ${CONFIG[PORT]}"
      echo "TLS           = ${CONFIG[TLS]}"
      echo ""
      echo "[paths]"
      echo "BaseDir       = $VARDIR/Mosek"
      echo "# Task files and results are stored in directories under JobsDir"
      echo "JobsDir       = \$BaseDir/jobs"
      echo "# HTML location."
      echo "# - HTMLTemplate defines the location of the HTML template used for framing HTML content."
      echo "# - HTMLContentDir defines a directory. Content can be in any subdirectory of this dir. From"
      echo "#   the clients point of view this is '/web/...'"
      echo "# - StaticHTMLDir defines the location of the static content (HTML, javascript, css, "
      echo "#   images etc). From the client point of view this is anything else under '/'"
      if [[ "${CONFIG[MODE]}" != "gui" ]]; then
          echo "HTMLTemplate  = "
          echo "StaticHTMLDir = \$BaseDir/HTML/default"
          echo "IndexHTML    =  /index.html"
      else
          echo "HTMLTemplate  = \$BaseDir/HTML/template/default.html"
          echo "StaticHTMLDir = \$BaseDir/HTML/web"
      fi
      echo "HTMLContentDir = \$BaseDir/HTML/content"
      if [ -n "${CONFIG[CERTFILE]}" -a -n "${CONFIG[KEYFILE]}" ]; then
          echo "# Define the locations of the TLS key and certificate"
          echo "CertDir       = $ETCDIR/Mosek/ssl"
          echo "CertFile      = \$CertDir/cert.pem"
          echo "KeyFile       = \$CertDir/key.pem"
      fi
      echo "# The PostgreSQL database connection string"
      echo "Database      = postgres:${CONFIG[DBRES]}"
      echo "# Defines the location of the server log file, where '-' means stdout"
      echo "LogFile       = \$BaseDir/server.log"
      echo "# The server's process ID is written to this file"
      echo "PidFile       = \$BaseDir/server.pid"
      echo "MosekBinDir   = ${CONFIG[MOSEKBINDIR]}"
      echo ""
      echo "[jobs]"
      echo "# This defines the command used to solve task files. Two symbols are recognized"
      echo "# by the server:"
      echo "# - '\$workdir' which is replaced by the job directory where files, including"
      echo "#   results, can be written"
      echo "# - '\#taskfile' is the full path to the problem file"
      echo "# The command should accept all formats recognized by MOSEK, and upon successful"
      echo "# completion, should write in \$workdir/ the files:"
      echo "# - Solutions are required if the solver successfully produced any result:"
      echo "#   - solution.jtask Solution in JSON/Task format"
      echo "#   - solution.task Solution in Task format"
      echo "#   - solution.ascii Solution in a human readable format, not necessarily machine"
      echo "#     readable"
      echo "# - result.res Required if solver ended normally (i.e. no SEGFAULT or similar)."
      echo "#   The optimizer result, a MSKrescode name as string, including 'MSK_RES_'"
      echo "# - result.trm The optimizer termination code, a MSKrescode name as string."
      echo "#   Present if the solver ran."
      echo "# - result.msg In case of a solver error this defines an error message"
      echo "SolveCmd      = \${PATHS.MosekBinDir}/mosekcli \\"
      echo "                -pid-file \$\$workdir/PID \\"
      echo "                -log \$\$workdir/solver.log \\"
      echo "                -read \$\$taskfile \\"
      echo "                -solve:res=\$\$workdir/result.res,trm=\$\$workdir/result.trm,msg=\$\$workdir/result.msg \\"
      echo "                -write-solution:json  \$\$workdir/solution.jtask \\"
      echo "                -write-solution:task  \$\$workdir/solution.task \\"
      echo "                -write-solution:task+zstd  \$\$workdir/solution.task.zst \\"
      echo "                -write-solution:plain \$\$workdir/solution.ascii"
      echo "# House keeping. If 'KeepJobs' > 0, a cleaning process will run in the"
      echo "# background with a frequency defined by 'CleaningDelay', and reduce the number"
      echo "# of jobs kept to this amount, deleting old jobs first."
      echo "KeepJobs      = 1000"
      echo "CleaningDelay = 30 minutes"
      echo "# Job queue defines the size of the queue for jobs waiting to run. A job enters"
      echo "# the queue not when submitted but rather when it is solved (by '/api/solve' or"
      echo "# '/api/solve-background'). JobWorkers define the maximum number of concurrent"
      echo "# solves."
      echo "JobQueue      = 100"
      echo "JobWorkers    = 2"

      echo "MaxFileSize   = 0 Mb"
      echo "Expiry        = 2 days"
      echo "Timeout       = 1 minute"
      echo ""
      echo "[access]"
      echo "# Allow retrieving submitted problems using the job token"
      echo "EnableGetProblem      = ${CONFIG[GETTASK]}"
      echo "# Allow/disallow non-authenticated submits"
      if   [ "${CONFIG[MODE]}" == "simple" ]; then
          echo "EnableAnonymousSubmit = yes"
      else
          echo "EnableAnonymousSubmit = ${CONFIG[ANONSUBMIT]}"
      fi

      echo "# The user api is the functionality accessed unser '/users/api/'. This turns on"
      echo "# user functionality like authentication, per-user permissions, login and"
      echo "# access token creation"
      if   [ "${CONFIG[MODE]}" == "simple" ]; then
          echo "EnableUserAPI         = no"
          #echo "EnableUserGUI         = no"
      elif [ "${CONFIG[MODE]}" == "api" ]; then
          echo "EnableUserAPI         = yes"
          #echo "EnableUserGUI         = no"
      elif [ "${CONFIG[MODE]}" == "gui" ]; then
          echo "EnableUserAPI         = yes"
          #echo "EnableUserGUI         = yes"
      fi
      echo "# "
      echo "AccessTokenMaxExpiry  = 3 months" ) > $CONFIGFILE
    fi && \
\
echo "Configuration file : $CONFIGFILE" && \
echo "Listen to port:      ${CONFIG[PORT]}" && \
echo "Server work dir:     $VARDIR/Mosek" && \
echo "Database:            ${CONFIG[DB]}:${CONFIG[DBRES]}" && \
if   [ "${CONFIG[MODE]}" == "simple" ]; then
    echo "API Mode:            simple"
elif [ "${CONFIG[MODE]}" == "api" ]; then
    echo "API Mode:            API enabled"
elif [ "${CONFIG[MODE]}" == "gui" ]; then
    echo "API Mode:            API+GUI enabled"
fi && \
if   [ "${CONFIG[CERTFILE]}" != "" ]; then
    echo "Serve mode:          HTTPS"
else
    echo "Serve mode:          HTTP"
fi
echo "Found MOSEK:         ${CONFIG[MOSEKBINDIR]}/mosekcli"
echo && \
echo "You can now start the server as:" &&\
echo "$BINDIR/MosekServer" \
|| \
echo "Installation failed!"
