#!/usr/bin/env bash
# Copyright (c) 2002-2016 "Neo Technology,"
# Network Engine for Objects in Lund AB [http://neotechnology.com]
#
# This file is part of Neo4j.
#
# Neo4j is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Callers may provide the following environment variables to customize this script:
#  * JAVA_HOME
#  * JAVA_CMD
#  * NEO4J_HOME
#  * NEO4J_CONF
#  * NEO4J_START_WAIT

set -euo pipefail
[[ "${TRACE:-}" ]] && set -x

: "${NEO4J_BIN:=$(dirname "$0")}"
readonly NEO4J_BIN
. "${NEO4J_BIN}/neo4j-shared.sh"

setup_arbiter_options() {
  is_arbiter() {
    compgen -G "${NEO4J_LIB}/neo4j-server-enterprise-*.jar" >/dev/null && \
      [[ "$(echo "${dbms_mode:-}" | tr [:lower:] [:upper:])" == "ARBITER" ]]
  }

  if is_arbiter; then
    SHUTDOWN_TIMEOUT=20
    MIN_ALLOWED_OPEN_FILES=1
    MAIN_CLASS="org.neo4j.server.enterprise.ArbiterEntryPoint"

    print_start_message() {
      echo "Started in ARBITER mode."
      echo "This instance is now joining the cluster."
    }
  else
    SHUTDOWN_TIMEOUT=120
    MIN_ALLOWED_OPEN_FILES=40000
    MAIN_CLASS="org.neo4j.server.CommunityEntryPoint"

    print_start_message() {
      port="${org_neo4j_server_webserver_port:-7474}"
      NEO4J_SERVER_ADDRESS="${org_neo4j_server_webserver_address:-localhost}"

      echo "Started neo4j (pid ${NEO4J_PID}). By default, it is available at http://${NEO4J_SERVER_ADDRESS}:${port}/"

      if [[ "$(echo "${dbms_mode:-}" | tr [:lower:] [:upper:])" == "HA" ]]; then
        echo "This HA instance will be operational once it has joined the cluster."
      else
        echo "There may be a short delay until the server is ready."
      fi
    }
  fi
}

check_status() {
  if [ -e "${NEO4J_PIDFILE}" ] ; then
    NEO4J_PID=$(cat "${NEO4J_PIDFILE}")
    kill -0 "${NEO4J_PID}" 2>/dev/null || unset NEO4J_PID
  fi
}

check_limits() {
  detect_os
  if [ "${DIST_OS}" != "macosx" ] ; then
    ALLOWED_OPEN_FILES="$(ulimit -n)"

    if [ "${ALLOWED_OPEN_FILES}" -lt "${MIN_ALLOWED_OPEN_FILES}" ]; then
      echo "WARNING: Max ${ALLOWED_OPEN_FILES} open files allowed, minimum of ${MIN_ALLOWED_OPEN_FILES} recommended. See the Neo4j manual."
    fi
  fi
}

setup_java_opts() {
  JAVA_OPTS=("-server")

  [[ -n "${dbms_memory_heap_initial_size:-}" ]] && JAVA_MEMORY_OPTS+=("-Xms${dbms_memory_heap_initial_size}m")
  [[ -n "${dbms_memory_heap_max_size:-}" ]] && JAVA_MEMORY_OPTS+=("-Xmx${dbms_memory_heap_max_size}m")
  [[ -n "${JAVA_MEMORY_OPTS:-}" ]] && JAVA_OPTS+=("${JAVA_MEMORY_OPTS[@]}")

  if [[ "${dbms_logs_gc_enabled:-}" = "true" ]]; then
    JAVA_OPTS+=("-Xloggc:${NEO4J_LOGS}/gc.log" \
                "-XX:+UseGCLogFileRotation" \
                "-XX:NumberOfGCLogFiles=${dbms_logs_gc_rotation_keep_number:-5}" \
                "-XX:GCLogFileSize=${dbms_logs_gc_rotation_size:-20m}")
    if [[ -n "${dbms_logs_gc_options:-}" ]]; then
      JAVA_OPTS+=(${dbms_logs_gc_options}) # unquoted to split on spaces
    else
      JAVA_OPTS+=("-XX:+PrintGCDetails" "-XX:+PrintGCDateStamps" "-XX:+PrintGCApplicationStoppedTime" \
                  "-XX:+PrintPromotionFailure" "-XX:+PrintTenuringDistribution")
    fi
  fi

  if [[ -n "${dbms_jvm_additional:-}" ]]; then
    JAVA_OPTS+=(${dbms_jvm_additional}) # unquoted to split on spaces
  fi
}

assemble_command_line() {
  retval=("${JAVA_CMD}" "-cp" "${CLASSPATH}" "${JAVA_OPTS[@]}" "-Dfile.encoding=UTF-8" "${MAIN_CLASS}" \
          "--config-dir=${NEO4J_CONF}")
}

do_console() {
  check_status
  if [[ "${NEO4J_PID:-}" ]] ; then
    echo "Neo4j is already running (pid ${NEO4J_PID})."
    exit 1
  fi

  echo "Starting Neo4j."

  check_limits
  build_classpath

  assemble_command_line
  command_line=("${retval[@]}")
  exec "${command_line[@]}"
}

do_start() {
  check_status
  if [[ "${NEO4J_PID:-}" ]] ; then
    echo "Neo4j is already running (pid ${NEO4J_PID})."
    exit 0
  fi

  echo "Starting Neo4j."

  check_limits
  build_classpath

  assemble_command_line
  command_line=("${retval[@]}")
  nohup "${command_line[@]}" >>"${CONSOLE_LOG}" 2>&1 &
  echo "$!" >"${NEO4J_PIDFILE}"

  : "${NEO4J_START_WAIT:=5}"
  end="$((SECONDS+NEO4J_START_WAIT))"
  while true; do
    check_status

    if [[ "${NEO4J_PID:-}" ]]; then
      break
    fi

    if [[ "${SECONDS}" -ge "${end}" ]]; then
      echo "Unable to start. See ${CONSOLE_LOG} for details."
      rm "${NEO4J_PIDFILE}"
      return 1
    fi

    sleep 1
  done

  print_start_message
  echo "See ${CONSOLE_LOG} for current status."
}

do_stop() {
  check_status

  if [[ ! "${NEO4J_PID:-}" ]] ; then
    echo "Neo4j not running"
    [ -e "${NEO4J_PIDFILE}" ] && rm "${NEO4J_PIDFILE}"
    return 0
  else
    echo -n "Stopping Neo4j."
    end="$((SECONDS+SHUTDOWN_TIMEOUT))"
    while true; do
      check_status

      if [[ ! "${NEO4J_PID:-}" ]]; then
        echo " stopped"
        [ -e "${NEO4J_PIDFILE}" ] && rm "${NEO4J_PIDFILE}"
        return 0
      fi

      kill "${NEO4J_PID}" 2>/dev/null || true

      if [[ "${SECONDS}" -ge "${end}" ]]; then
        echo " failed to stop"
        echo "Neo4j (pid ${NEO4J_PID}) took more than ${SHUTDOWN_TIMEOUT} seconds to stop."
        echo "Please see ${CONSOLE_LOG} for details."
        return 1
      fi

      echo -n "."
      sleep 1
    done
  fi
}

do_status() {
  check_status
  if [[ ! "${NEO4J_PID:-}" ]] ; then
    echo "Neo4j is not running"
    exit 3
  else
    echo "Neo4j is running at pid ${NEO4J_PID}"
  fi
}

main() {
  setup_environment
  CONSOLE_LOG="$(cd "${NEO4J_LOGS}" && pwd)/neo4j.log"
  NEO4J_PIDFILE="${NEO4J_RUN}/neo4j.pid"
  readonly CONSOLE_LOG NEO4J_PIDFILE

  setup_java_opts
  check_java
  setup_arbiter_options

  case "${1:-}" in
    console)
      do_console
      ;;

    start)
      do_start
      ;;

    stop)
      do_stop
      ;;

    restart)
      do_stop
      do_start
      ;;

    status)
      do_status
      ;;

    *)
      echo "Usage: ${PROGRAM} { console | start | stop | restart | status }"
      ;;
  esac
}

main "$@"
