#!/usr/bin/env bash

#
# Install JDK for Linux and Mac OS
#
# This script determines the most recent early-access build number,
# downloads the JDK archive to the user home directory and extracts
# it there.
#
# Exported environment variables (when sourcing this script)
#
#   JAVA_HOME is set to the extracted JDK directory
#   PATH is prepended with ${JAVA_HOME}/bin
#
# (C) 2018 Christian Stein
#
# https://github.com/sormuras/bach/blob/master/install-jdk.sh
#

set -o errexit
#set -o nounset # https://github.com/travis-ci/travis-ci/issues/5434
#set -o xtrace

function initialize() {
    readonly script_name="$(basename "${BASH_SOURCE[0]}")"
    readonly script_version='2018-10-17'

    dry=false
    silent=false
    verbose=false
    emit_java_home=false

    feature='ea'
    license='GPL'
    os='?'
    url='?'
    workspace="${HOME}"
    target='?'
    cacerts=false
}

function usage() {
cat << EOF
Usage: ${script_name} [OPTION]...
Download and extract the latest-and-greatest JDK from java.net or Oracle.

Version: ${script_version}
Options:
  -h|--help                 Displays this help
  -d|--dry-run              Activates dry-run mode
  -s|--silent               Displays no output
  -e|--emit-java-home       Print value of "JAVA_HOME" to stdout (ignores silent mode)
  -v|--verbose              Displays verbose output

  -f|--feature 9|10|...|ea  JDK feature release number, defaults to "ea"
  -l|--license GPL|BCL      License defaults to "GPL", BCL also indicates OTN-LA for Oracle Java SE
  -o|--os linux-x64|osx-x64 Operating system identifier (works best with GPL license)
  -u|--url "https://..."    Use custom JDK archive (provided as .tar.gz file)
  -w|--workspace PATH       Working directory defaults to \${HOME} [${HOME}]
  -t|--target PATH          Target directory, defaults to first component of the tarball
  -c|--cacerts              Link system CA certificates (currently only Debian/Ubuntu is supported)
EOF
}

function script_exit() {
    if [[ $# -eq 1 ]]; then
        printf '%s\n' "$1"
        exit 0
    fi

    if [[ $# -eq 2 && $2 =~ ^[0-9]+$ ]]; then
        printf '%b\n' "$1"
        exit "$2"
    fi

    script_exit 'Invalid arguments passed to script_exit()!' 2
}

function say() {
    if [[ ${silent} != true ]]; then
        echo "$@"
    fi
}

function verbose() {
    if [[ ${verbose} == true ]]; then
        echo "$@"
    fi
}

function parse_options() {
    local option
    while [[ $# -gt 0 ]]; do
        option="$1"
        shift
        case ${option} in
            -h|-H|--help)
                usage
                exit 0
                ;;
            -v|-V|--verbose)
                verbose=true
                ;;
            -s|-S|--silent)
                silent=true
                verbose "Silent mode activated"
                ;;
            -d|-D|--dry-run)
                dry=true
                verbose "Dry-run mode activated"
                ;;
            -e|-E|--emit-java-home)
                emit_java_home=true
                verbose "Emitting JAVA_HOME"
                ;;
            -f|-F|--feature)
                feature="$1"
                verbose "feature=${feature}"
                shift
                ;;
            -l|-L|--license)
                license="$1"
                verbose "license=${license}"
                shift
                ;;
            -o|-O|--os)
                os="$1"
                verbose "os=${os}"
                shift
                ;;
            -u|-U|--url)
                url="$1"
                verbose "url=${url}"
                shift
                ;;
            -w|-W|--workspace)
                workspace="$1"
                verbose "workspace=${workspace}"
                shift
                ;;
            -t|-T|--target)
                target="$1"
                verbose "target=${target}"
                shift
                ;;
            -c|-C|--cacerts)
                cacerts=true
                verbose "Linking system CA certificates"
                ;;
            *)
                script_exit "Invalid argument was provided: ${option}" 2
                ;;
        esac
    done
}

function determine_latest_jdk() {
    local number
    local curl_result
    local url

    verbose "Determine latest JDK feature release number"
    number=9
    while [[ ${number} != 99 ]]
    do
      url=http://jdk.java.net/${number}
      curl_result=$(curl -o /dev/null --silent --head --write-out %{http_code} ${url})
      if [[ ${curl_result} -ge 400 ]]; then
        break
      fi
      verbose "  Found ${url} [${curl_result}]"
      latest_jdk=${number}
      number=$[$number +1]
    done

    verbose "Latest JDK feature release number is: ${latest_jdk}"
}

function perform_sanity_checks() {
    if [[ ${feature} == '?' ]] || [[ ${feature} == 'ea' ]]; then
        feature=${latest_jdk}
    fi
    if [[ ${feature} -lt 9 ]] || [[ ${feature} -gt ${latest_jdk} ]]; then
        script_exit "Expected feature release number in range of 9 to ${latest_jdk}, but got: ${feature}" 3
    fi
    if [[ -d "$target" ]]; then
        script_exit "Target directory must not exist, but it does: $(du -hs '${target}')" 3
    fi
}

function determine_url() {
    local DOWNLOAD='https://download.java.net/java'
    local ORACLE='http://download.oracle.com/otn-pub/java/jdk'

    # Archived feature or official GA build?
    case "${feature}-${license}" in
        9-GPL) url="${DOWNLOAD}/GA/jdk9/9.0.4/binaries/openjdk-9.0.4_${os}_bin.tar.gz"; return;;
        9-BCL) url="${ORACLE}/9.0.4+11/c2514751926b4512b076cc82f959763f/jdk-9.0.4_${os}_bin.tar.gz"; return;;
       10-GPL) url="${DOWNLOAD}/GA/jdk10/10.0.2/19aef61b38124481863b1413dce1855f/13/openjdk-10.0.2_${os}_bin.tar.gz"; return;;
       10-BCL) url="${ORACLE}/10.0.2+13/19aef61b38124481863b1413dce1855f/jdk-10.0.2_${os}_bin.tar.gz"; return;;
       11-GPL) url="${DOWNLOAD}/GA/jdk11/13/GPL/openjdk-11.0.1_${os}_bin.tar.gz"; return;;
       11-BCL) url="${ORACLE}/11.0.1+13/90cf5d8f270a4347a95050320eef3fb7/jdk-11.0.1_${os}_bin.tar.gz"; return;;
    esac

    # EA or RC build?
    local JAVA_NET="http://jdk.java.net/${feature}"
    local candidates=$(wget --quiet --output-document - ${JAVA_NET} | grep -Eo 'href[[:space:]]*=[[:space:]]*"[^\"]+"' | grep -Eo '(http|https)://[^"]+')
    url=$(echo "${candidates}" | grep -Eo "${DOWNLOAD}/.+/jdk${feature}/.+/${license}/.*jdk-${feature}.+${os}_bin.tar.gz$" || true)

    if [[ -z ${url} ]]; then
        script_exit "Couldn't determine a download url for ${feature}-${license} on ${os}" 1
    fi
}

function prepare_variables() {
    if [[ ${os} == '?' ]]; then
        if [[ "$OSTYPE" == "darwin"* ]]; then
          os='osx-x64'
        else
          os='linux-x64'
        fi
    fi
    if [[ ${url} == '?' ]]; then
        determine_latest_jdk
        perform_sanity_checks
        determine_url
    else
        feature='<overridden by custom url>'
        license='<overridden by custom url>'
        os='<overridden by custom url>'
    fi
    archive="${workspace}/$(basename ${url})"
    status=$(curl -o /dev/null --silent --head --write-out %{http_code} ${url})
}

function print_variables() {
cat << EOF
Variables:
  feature = ${feature}
  license = ${license}
       os = ${os}
      url = ${url}
   status = ${status}
  archive = ${archive}
EOF
}

function download_and_extract_and_set_target() {
    local quiet='--quiet'; if [[ ${verbose} == true ]]; then quiet=''; fi
    local local="--directory-prefix ${workspace}"
    local remote='--timestamping --continue'
    local wget_options="${quiet} ${local} ${remote}"
    local tar_options="--file ${archive}"

    say "Downloading JDK from ${url}..."
    verbose "Using wget options: ${wget_options}"
    if [[ ${license} == 'GPL' ]]; then
        wget ${wget_options} ${url}
    else
        wget ${wget_options} --header "Cookie: oraclelicense=accept-securebackup-cookie" ${url}
    fi

    verbose "Using tar options: ${tar_options}"
    if [[ ${target} == '?' ]]; then
        tar --extract ${tar_options} -C "${workspace}"
        if [[ "$OSTYPE" != "darwin"* ]]; then
            target="${workspace}"/$(tar --list ${tar_options} | grep 'bin/javac' | tr '/' '\n' | tail -3 | head -1)
        else
            target="${workspace}"/$(tar --list ${tar_options} | head -2 | tail -1 | cut -f 2 -d '/' -)/Contents/Home
        fi
    else
        if [[ "$OSTYPE" != "darwin"* ]]; then
            mkdir --parents "${target}"
            tar --extract ${tar_options} -C "${target}" --strip-components=1
        else
            mkdir -p "${target}"
            tar --extract ${tar_options} -C "${target}" --strip-components=4 # . / <jdk> / Contents / Home
        fi
    fi

    if [[ ${verbose} == true ]]; then
        echo "Set target to: ${target}"
        echo "Content of target directory:"
        ls "${target}"
        echo "Content of release file:"
        [[ ! -f "${target}/release" ]] || cat "${target}/release"
    fi

    # Link to system certificates
    # http://openjdk.java.net/jeps/319
    # https://bugs.openjdk.java.net/browse/JDK-8196141
    # TODO: Provide support for other distributions than Debian/Ubuntu
    if [[ ${cacerts} == true ]]; then
        mv "${target}/lib/security/cacerts" "${target}/lib/security/cacerts.jdk"
        ln -s /etc/ssl/certs/java/cacerts "${target}/lib/security/cacerts"
    fi
}

function main() {
    initialize
    say "$script_name $script_version"

    parse_options "$@"
    prepare_variables

    if [[ ${silent} == false ]]; then print_variables; fi
    if [[ ${dry} == true ]]; then exit 0; fi

    download_and_extract_and_set_target

    export JAVA_HOME=$(cd "${target}"; pwd)
    export PATH=${JAVA_HOME}/bin:$PATH

    if [[ ${silent} == false ]]; then java -version; fi
    if [[ ${emit_java_home} == true ]]; then echo "${JAVA_HOME}"; fi
}

main "$@"