#!/bin/bash

# standard FWD server startup script

# defaults
whole_path="$(cd "$(dirname "$0")"; pwd)/$(basename "$0")"
srcname=$(basename "$whole_path")
srcpath=$(dirname "$whole_path")
[ ! -f ${srcpath}/appname ] && echo "FATAL: No appname to source" && exit
[ -z "$appname" ] && . ${srcpath}/appname
suspend="n"
hprof=""
prog="java"
dump="-XX:+HeapDumpOnOutOfMemoryError"
srvr="-server"
agent=""
heap=8192
cfg="server.xml"
port=""
portbase=3333
portstep=100
mode=""
statcode="n"
instance=0
batch=""
test=0
appjar=$appname
applib=""
open_api=""
entrypoint="com.goldencode.p2j.main.FwdLauncher"
appcds=0
max_classes_default=100000
cdsparams=""
jmx_port=22100
gc_log="server_garbage_collection_stats.log"
archive_retention=3
# Default log directories
default_log_dirs=".:./logs:../logs"

# Function to clear *.log* in each directory passed (':' separated list of directories)
clear_logs()
{
   local paths
   IFS=':' read -r -a paths <<< "${@}"
   for path in "${paths[@]}"; do
      if [ -d "$path" ]; then
         log_files=("${path}"/*.log)
         [ ${#log_files[@]} -gt 0 ] && [ -f "${log_files[0]}" ] && rm -f ${path}/*.log*
      fi
   done
}

# Ensure UTF-8 is set
ensure_utf8_lang()
{
   # Get the current LANG setting and encoding portion (after the dot '.')
   current_lang="${LANG}"
   encoding="${current_lang##*.}"

   shopt -s nocasematch
   # Check if the encoding is not UTF-8
   if [[ ! "$encoding" =~ ^utf-?8$ ]]; then
      # Warn the user
      echo "Warning: The current LANG setting (${current_lang}) is not using UTF-8 encoding."

      # Set LANG to LANGUAGE.UTF-8, or fallback to en_US.UTF-8 if LANGUAGE is not set
      [ -z "$LANGUAGE" ] && new_lang="en_US.UTF-8" || new_lang="${LANGUAGE}.UTF-8"

      # Apply the new LANG setting
      export LANG="$new_lang"

      # Inform the user
      echo "LANG has been updated to: ${LANG}"
   fi
   shopt -u nocasematch
}

# Setup rolling generation for the archive
roll_cds_archive()
{
   local archive_file=$1
   local retention=$2

   if [ -f "${archive_file}.${retention}" ]; then
      rm -f "${archive_file}.${retention}"
   fi
   for ((i=retention-1; i>=0; i--)); do
      if [ -f "${archive_file}.${i}" ]; then
         mv "${archive_file}.${i}" "${archive_file}.$((i+1))"
      fi
   done
   if [ -f "${archive_file}" ]; then
      mv "${archive_file}" "${archive_file}.0"
   fi
}

# Setup Application Class Data Sharing
setup_appcds()
{
   local appcds=$1
   local max_classes=$2

   unfiltered_cds="cds-class-list-unedited.lst" 
   [ -f "cds-blacklist.lst" ] && cds_filter=$(cat "cds-blacklist.lst") || cds_filter=""
   filtered_cds="cds-class-list-filtered.lst" 
   cds_archive="cds-archive.jsa" 
   xlog=""
   if [ $appcds -eq 1 ]; then
      # Full recording
      xshare="-Xshare:off" 
      xxdumploaded="-XX:DumpLoadedClassList=${unfiltered_cds}" 

      # Perform rolling archive
      roll_cds_archive $cds_archive $archive_retention
   elif [ $appcds -eq 2 ]; then
      # Archive previous full recording
      if [ ! -f $unfiltered_cds ]; then
         echo "File $unfiltered_cds is missing. Make sure you started the server with parameter \"-x1\" at least once before." 
         exit 1
      else
         echo "Using class list created at $(stat -c "%w" $unfiltered_cds | cut -d '.' -f 1)".
      fi
      awk 'length($0) <= 4096' $unfiltered_cds | head -n $max_classes > $filtered_cds
      for p in $cds_filter; do
         sed -i "/$p/d" $filtered_cds
      done
      xshare="-Xshare:dump" 
      xxsharedclass="-XX:SharedClassListFile=${filtered_cds}" 
      xxsharedarchive="-XX:SharedArchiveFile=$cds_archive" 
      xlog="-Xlog:cds:file=cds.log -Xlog:class+load:file=cds-class-load.log"
      output_redir="> /dev/null 2>&1"
   elif [ $appcds -eq 3 ]; then
      if [ ! -f $cds_archive ]; then
         echo "Archive $cds_archive is missing. Make sure you have previously started the server with parameter \"-x2\"." 
         exit 1
      else
         echo "Using archive created at $(stat -c "%w" $cds_archive | cut -d '.' -f 1)".
      fi
      # Start with previous archived list
      xshare="-Xshare:on" 
      xxsharedarchive="-XX:SharedArchiveFile=$cds_archive" 
   fi
   # Setup the cds parameters from above
   cdsparams="${xshare} ${xxsharedarchive} ${xxdumploaded} ${xxsharedclass} ${xlog}" 
}

show_usage()
{
   i=0;        usage[$i]="\nUsage: $0 [-dyptksw1a] [-o<path>] [-m{1|0}] [-b<process>] [-z<cp>] [-h<heap>] [-c<cfg>] [-i<instance>] [-r<profile>] [-j<jar>]
[--gc] [--jmx_port=<port>] [-x{1|2 [max_classes]|3}]"
   i=$((i+1)); usage[$i]="\nStart the FWD server."
   i=$((i+1)); usage[$i]="\nWhere:"
   i=$((i+1)); usage[$i]="\t-d = enable JVM debug mode (debugger port will be set as 2080 + instance #)"
   i=$((i+1)); usage[$i]="\t-y = suspend the JVM on startup when debug mode is enabled (does not suspend by default)"
   i=$((i+1)); usage[$i]="\t-p = enable JVM hprof (profiling output to gc.log)"
   i=$((i+1)); usage[$i]="\t-t = test mode (displays the command but doesn't execute)"
   i=$((i+1)); usage[$i]="\t-k = kill mode (shuts down the specified/default instance)"
   i=$((i+1)); usage[$i]="\t-s = status mode (displays the status of the specified/default instance)"
   i=$((i+1)); usage[$i]="\t-w = wait mode (does not return until the specified/default instance is STATUS_RUNNING)"
   i=$((i+1)); usage[$i]="\t-1 = enable C1 HotSpot compiler (client compiler for the JVM)"
   i=$((i+1)); usage[$i]="\t-a = enable agent for method profiling (not for production use!)"
   i=$((i+1)); usage[$i]="\t-o = Enable heap dump on OOME to given path"
   i=$((i+1)); usage[$i]="\t-m1 = start collecting method profile data (must have started server with -a)"
   i=$((i+1)); usage[$i]="\t-m0 = stop collecting method profile data (must have started server with -a)"
   i=$((i+1)); usage[$i]="\t-b = launch the batch program <process>";
   i=$((i+1)); usage[$i]="\t-z = override the classpath with <cp>. See cpath.txt for the default contents. $(echo $cpath > cpath.txt)"
   i=$((i+1)); usage[$i]="\t-h = set JVM max heap size to <heap> (must be an integer number of MB)"
   i=$((i+1)); usage[$i]="\t-c = use FWD bootstrap config file <cfg> (defaults to server.xml)"
   i=$((i+1)); usage[$i]="\t-i = server instance number (0..9, assigns the FWD server port, defaults to 0)"
   i=$((i+1)); usage[$i]="\t-r = pass <profile> to ServerDriver via \"-profile\""
   i=$((i+1)); usage[$i]="\t-j = main application jar name (default is ${appjar})"
   i=$((i+1)); usage[$i]="\t-x1 = appcds (Application Class Data Sharing): Record the name of every class loaded by the VM until the server is stopped (Java 11 or higher)"
   i=$((i+1)); usage[$i]="\t-x2 = appcds: Create an archive of the classes recorded via -x1; specify the number of classes included, default to $max_classes_default"
   i=$((i+1)); usage[$i]="\t-x3 = appcds: Loads at startup the archive of classes created via -x2"
   i=$((i+1)); usage[$i]="\t--gc monitor garbage collection statistics via Java Management Extensions (JMX). Data logged to file \"${gc_log}\""
   i=$((i+1)); usage[$i]="\t--jmx_port=<port> Set JMX port to <port> (default is ${jmx_port})"
   i=$((i+1)); usage[$i]="\t--clear_logs[=<dirlist>] Clear '*.log*' files in directories listed, each seperated by a colon (default is ${default_log_dirs})"
   i=$((i+1)); usage[$i]="\n -? or --help = show usage."

   for i in "${usage[@]}"; do
      echo -e $i
   done
}

# process options
while getopts "?dyptksw1a:z:h:c:i:b:m:o:j:r:x:-:" opt; do
   case $opt in
      d  ) debug=true ;;
      y  ) suspend="y" ;;
      p  ) hprof="-Xrunhprof:heap=dump,doe=y,format=b,file=gc.log" ;;
      t  ) test=1 ;;
      k  ) mode="-k"; statcode="y" ;;
      s  ) mode="-s"; statcode="y" ;;
      w  ) mode="-w -1"; statcode="y" ;;
      b  ) batch="-b $OPTARG" ;;
      a  ) agent="-javaagent:${OPTARG}" ;;
      m  ) mode="-m $OPTARG"; statcode="y" ;;
      o  ) dump+=" -XX:HeapDumpPath=${OPTARG}" ;;
      1  ) srvr="" ;;
      z  ) cpath=$OPTARG ;;
      h  ) heap=$OPTARG ;;
      c  ) cfg=$OPTARG ;; 
      i  ) instance=$OPTARG ;;
      j  ) appjar=$OPTARG ;;
      r  ) profile="-profile $OPTARG" ;;
      x  ) appcds=$OPTARG
           if [[ ${!OPTIND} =~ ^[0-9]+$ ]]; then
              max_classes=${!OPTIND}
              OPTIND=$((OPTIND + 1))
           else
              max_classes=$max_classes_default
           fi
           ;;
      -  ) case ${OPTARG} in
              "gc"         ) enable_gc_stats=true ;;
              "jmx_port="* ) jmx_port=$(echo $OPTARG | cut -d "=" -f2) ;;
              "clear_logs"*) clear_logs_flag=true
                             if [[ "${OPTARG}" == "clear_logs="* ]]; then
                                log_dirs=$(echo $OPTARG | cut -d "=" -f2)
                             else
                                log_dirs=$default_log_dirs
                             fi ;;
              "help"       ) show_usage
                             exit 1 ;;
           esac ;;
      \? ) show_usage
           exit 1 ;;
   esac
done
shift $(($OPTIND - 1))

# Clear logs, if requested
if [ "$clear_logs_flag" = true ]; then
   clear_logs "$log_dirs"
fi

# getcp.sh and getspi.sh are helpers for setting up the classpath and spi for the given application. 
# This allows more flexibility in where the FWD configuration is placed. It can be one directory up,
# and then in lib, or in /opt/fwd/lib, or specified by FWD_LIB exported or up to the build directory,
# then down to lib. If the helper isn't found, we can only fall back to the original method.
helper=$(cd $srcpath/..; echo $(pwd))
export PATH=$helper:$PATH
if [ ."$(which getcp.sh)". == ".." ]; then
   cpath=""
   for f in ../lib/*.jar
   do
      if [ $f != "../lib/${appjar}.jar" ] ; then
         cpath=$cpath:$f
      fi
   done
   spi="-Djava.locale.providers=SPI,CLDR,COMPAT"
   # in case we are running Java 8 build on Java 11+ VM
   [[ "$cpath" != *fwdspi.jar* ]] && cpath="${cpath}:../lib/spi/fwdspi.jar"
else
   cpath=$(getcp.sh "$appjar" $srcpath )
   spi=$(getspi.sh $cpath)
fi

# Add extra jars, if needed
if [ -n "$applib" ] && [ -d "../${applib}" ]; then
   for f in ../${applib}/*.jar
   do
       cpath=$f:$cpath
   done
fi

# Additional parameters for Java 17
open_api="--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.naming/javax.naming=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED"

# final arg prep
cp="-classpath $cpath"
dport=$((2080 + $instance))
sport=$(($portbase + $instance))
iport=$(($portbase + $instance + $portstep))
port="net:connection:secure=true net:server:secure_port=$sport net:server:insecure_port=$iport"

if [ "$debug" == true ]; then
   [ -f "/.dockerenv" ] && daddress="0.0.0.0:${dport}" || daddress="${dport}"
   dtxt="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=${daddress},server=y,suspend=${suspend}" 
fi

perf="-XX:GCTimeRatio=19 -XX:MaxTenuringThreshold=7 -XX:StringTableSize=1000003"
maxheap="-Xmx"$heap"m"
if [ "$enable_gc_stats" = "true" ]; then
   jmxMonitoringD="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false"
   jmxMonitoringPort="-Dcom.sun.management.jmxremote.port=${jmx_port} -Dcom.sun.management.jmxremote.rmi.port=${jmx_port}"
   gcMonitoringXX="-XX:+PrintGCDetails"
   Xlog="-Xlog:gc*:file=${gc_log}:time" || Xlog="-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:${gc_log}"
   jmx="${jmxMonitoringD} ${jmxMonitoringPort} ${gcMonitoringXX} -verbose:gc ${Xlog}"
fi

# Determine if AppCDS (Application Class Data Sharing) will change startup
if [[ $appcds -ge 1 && $appcds -le 3 ]]; then
   setup_appcds $appcds $max_classes
fi

# Ensure UTF-8 encoding
original_lang="${LANG}"
ensure_utf8_lang

# run the FWD server
if [ $test -eq 1 ] ; then
   echo $prog $dump $perf $hprof $maxheap $srvr $jmx $dtxt $agent $spi $open_api $cp $cdsparams $entrypoint $mode $batch $cfg $port $profile "$@"
elif [ $statcode = "y" ]; then
   $prog $dump $perf $hprof $maxheap $srvr $jmx $dtxt $agent $spi $open_api $cp $cdsparams $entrypoint $mode $batch $cfg $port $profile "$@" 2> /dev/null
else
   [ -f "/usr/local/bin/set_spawner.sh" ] && sudo set_spawner.sh --classpath=${cpath}
   eval $prog $dump $perf $hprof $maxheap $srvr $jmx $dtxt $agent $spi $open_api $cp $cdsparams $entrypoint $mode $batch $cfg $port $profile "$@" $output_redir
fi

rc=$?

# Restore the original LANG setting, if sourced and set
[ "${BASH_SOURCE[0]}" != "${0}" ] && [ ."$original_lang". != ".." ] && export LANG="$original_lang"

if [ $statcode = "y" ] && [ $test -ne 1 ]; then
   case $rc in
      0  ) status="STATUS_RUNNING" ;;
      1  ) status="STATUS_TIMEOUT" ;;
      2  ) status="STATUS_UNKNOWN" ;;
      3  ) status="STATUS_STOPPED" ;;
   esac
   echo "Result =" $status
fi

# Check if the archive was created successfully when appcds=2
if [ $appcds -eq 2 ] && [ ! -f $cds_archive ]; then
   echo "Warning: Archive $cds_archive was not created successfully."
fi
