#!/usr/bin/env bash

# 注：本脚本安全透明, 只联网下载(仅初次使用)不上传, 不会破环环境(会申请安装curl,才会申请一次root权限), 本软件支持一键卸载
# Note: This script is safe and transparent, only downloads (if need) and does not upload, will not break the environment, and supports one-click uninstallation

#
#  CoraxJava - a Java Static Analysis Framework
#  Copyright (C) 2024.  Feysh-Tech Group
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2.1 of the License, or (at your option) any later version.
#
#  This library 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
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#

################################################################################
#
# Description: coraxjw.sh is a Corax Wrapper program.
# Author: notify-bibi
#
################################################################################

CORAX_VERSION=2.20
CORAX_JAVA_ARTIFACT_NAME="corax-java-cli-community-$CORAX_VERSION"
CORAX_JAVA_ARTIFACT_ZIP="$CORAX_JAVA_ARTIFACT_NAME.zip"
CORAX_JAVA_CLI_NAME="corax-cli-community-${CORAX_VERSION}.jar"
JDK_TARGET_DIR="${HOME}/.local/share"
CORAX_TARGET_DIR="${HOME}/.local/corax"
JVM_VERSION=jdk-17.0.3.1

OSS_URL_CORAX_JAVA_CLI_COMMUNITY="https://release.feysh.com/corax-java-group/corax-java-cli-community-$CORAX_VERSION.zip?OSSAccessKeyId=LTAI5tKF4FQ2CGnhMA7oU58p&Expires=1764732690&Signature=oxmJHTFa3J6rCC6Q%2FaUOnnMPM00%3D"
OSS_URL_JDK_DARWIN_X64="http://release.feysh.com/corax-java-group/jdk-17.0.3.1_macos-x64_bin.tar.gz?OSSAccessKeyId=LTAI5tKF4FQ2CGnhMA7oU58p&Expires=2023865870&Signature=pSaX7XqiEO%2BuZ5NsF0AsnkvKTB0%3D"
OSS_URL_JDK_DARWIN_AARCH64="http://release.feysh.com/corax-java-group/jdk-17.0.3.1_macos-aarch64_bin.tar.gz?OSSAccessKeyId=LTAI5tKF4FQ2CGnhMA7oU58p&Expires=2023865859&Signature=M41W3NYVzNfVR8wLpmOoiGTYxWo%3D"
OSS_URL_JDK_WIN_X64="http://release.feysh.com/corax-java-group/jdk-17.0.3.1_windows-x64_bin.zip?OSSAccessKeyId=LTAI5tKF4FQ2CGnhMA7oU58p&Expires=2023865879&Signature=mnWSUQlW8Mwme%2FCIuvaWD27bNjE%3D"
OSS_URL_JDK_LINUX_X64="http://release.feysh.com/corax-java-group/jdk-17.0.3.1_linux-x64_bin.tar.gz?OSSAccessKeyId=LTAI5tKF4FQ2CGnhMA7oU58p&Expires=2023865849&Signature=VKfQnzV5P7qKygBu9WODOzNPmDw%3D"
OSS_URL_JDK_LINUX_AARCH64="http://release.feysh.com/corax-java-group/jdk-17.0.3.1_linux-aarch64_bin.tar.gz?OSSAccessKeyId=LTAI5tKF4FQ2CGnhMA7oU58p&Expires=2023865822&Signature=E45ZFy%2BYpDRo7dm9%2Ftjg%2Bj1trMU%3D"


uninstall=false
# Attempt to set APP_HOME

# Resolve links: $0 may be a link
app_path=$0

# Need this for daisy-chained symlinks.
while
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
    [ -h "$app_path" ]
do
    ls=$( ls -ld "$app_path" )
    link=${ls#*' -> '}
    case $link in             #(
      /*)   app_path=$link ;; #(
      *)    app_path=$APP_HOME$link ;;
    esac
done

APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="CoraxJava"
APP_BASE_NAME=${0##*/}

DEFAULT_JVM_OPTS='-XX:+ExitOnOutOfMemoryError'
BUILD_DIR="/tmp/corax_temp"

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

die () {
    echo
    echo "$*"
    echo
    exit 1
} >&2

_msg() {
    local color_on
    local color_off='\033[0m' # Text Reset
    duration=$SECONDS
    h_m_s="$((duration / 3600))h$(((duration / 60) % 60))m$((duration % 60))s"
    time_now="$(date +%Y%m%d-%u-%T.%3N)"

    case "${1:-none}" in
    red | error | erro) color_on='\033[0;31m' ;;       # Red
    green | info) color_on='\033[0;32m' ;;             # Green
    yellow | warning | warn) color_on='\033[0;33m' ;;  # Yellow
    blue) color_on='\033[0;34m' ;;                     # Blue
    purple | question | ques) color_on='\033[0;35m' ;; # Purple
    cyan) color_on='\033[0;36m' ;;                     # Cyan
    orange) color_on='\033[1;33m' ;;                   # Orange
    step)
        ((++STEP))
        color_on="\033[0;36m[${STEP}] $time_now \033[0m"
        color_off=" $h_m_s"
        ;;
    time)
        color_on="[${STEP}] $time_now "
        color_off=" $h_m_s"
        ;;
    log)
        shift
        echo "$time_now $*" >>$me_log
        return
        ;;
    *)
        unset color_on color_off
        ;;
    esac
    [ "$#" -gt 1 ] && shift
    echo -e "${color_on}$*${color_off}"
}

warn () {
    _msg warn "$*"
} >&2

_is_root() {
    if [ "$(id -u)" -eq 0 ]; then
        unset use_sudo
        return 0
    else
        use_sudo=sudo
        return 1
    fi
}

_is_china() {
    # 判断当前机器的 ip 地址是否是国内, 备用选项: cip.cc
    local is_china_ip=$(curl -sSL http://myip.ipip.net | grep '中国' | wc -l | tr -d "[:space:]")
    if [[ $is_china_ip -ge 1 ]]; then
        return 0
    else
        return 1
    fi
}

_detect_os() {
    _is_root || use_sudo=sudo
    if [[ -e /etc/os-release ]]; then
        source /etc/os-release
        os_type="${ID}"
    elif [[ -e /etc/centos-release ]]; then
        os_type=centos
    elif [[ -e /etc/arch-release ]]; then
        os_type=arch
    elif [[ $OSTYPE == darwin* ]]; then
        os_type=macos
    fi
    pkgs=()
    case "$os_type" in
    debian | ubuntu | linuxmint)
        # RUN apt-get update && \
        #        apt-get -y install sudo dialog apt-utils
        # RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
        command -v curl >/dev/null || pkgs+=(curl)
        # command -v shc >/dev/null || $use_sudo apt-get install -qq -y shc

        if [[ "${#pkgs[*]}" -ne 0 ]]; then
            $use_sudo apt-get update -qq
            $use_sudo apt-get install -yqq apt-utils >/dev/null
            $use_sudo apt-get install -yqq "${pkgs[@]}" >/dev/null
        fi
        ;;
    centos | amzn | rhel | fedora)
        rpm -q epel-release >/dev/null || {
            if [ "$os_type" = amzn ]; then
                $use_sudo amazon-linux-extras install -y epel >/dev/null
            else
                $use_sudo yum install -y epel-release >/dev/null
                # DNF="dnf --setopt=tsflags=nodocs -y"
                # $DNF install epel-release
            fi
        }
        command -v curl >/dev/null || pkgs+=(curl)
        if [[ "${#pkgs[*]}" -ne 0 ]]; then
            $use_sudo yum install -y "${pkgs[@]}" >/dev/null
        fi
        ;;
    alpine)
        command -v curl >/dev/null || pkgs+=(curl)
        if [[ "${#pkgs[*]}" -ne 0 ]]; then
            $use_sudo apk add --no-cache "${pkgs[@]}" >/dev/null
        fi
        ;;
    macos)
        command -v curl >/dev/null || pkgs+=(curl)
        if (("${#pkgs[*]}")); then
            brew install "${pkgs[@]}"
        fi
        ;;
    arch)
        command -v curl >/dev/null || pkgs+=(curl)
        if (("${#pkgs[*]}")); then
            $use_sudo pacman -S "${pkgs[@]}"
        fi
        ;;
    *)
        echo "Looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2 or Arch Linux system"
        _msg error "Unsupported. exit."
        exit 1
        ;;
    esac


    # OS specific support (must be 'true' or 'false').
    cygwin=false
    msys=false
    darwin=false
    nonstop=false
    case "$( uname )" in                #(
      CYGWIN* )         cygwin=true  ;; #(
      Darwin* )         darwin=true  ;; #(
      MSYS* | MINGW* )  msys=true    ;; #(
      NONSTOP* )        nonstop=true ;;
    esac
}

_download_extract() {
    local name=$1
    local url=$2
    local dest=$3
    local temp_file="$BUILD_DIR/$4"
    local download_flag="$BUILD_DIR/$4.flag"

    set -e
    mkdir -p "$BUILD_DIR"
    if [ -e "$download_flag" ] && [ ! -e "$temp_file" ] ; then
        rm -f "$download_flag"
    fi
    if [ ! -e "$download_flag" ] || [ -z "$(ls "$dest")" ] ; then
        rm -f "$temp_file"
        _msg step "[Downloading] Downloading $name : $url to $temp_file"
        if command -v curl >/dev/null 2>&1; then
            if [ -t 1 ]; then CURL_PROGRESS="--progress-bar"; else CURL_PROGRESS="--show-error"; fi
            # shellcheck disable=SC2086
            curl $CURL_PROGRESS -L --fail --output "${temp_file}" "$url" 2>&1
        elif command -v wget >/dev/null 2>&1; then
            if [ -t 1 ]; then WGET_PROGRESS=""; else WGET_PROGRESS="-nv"; fi
            wget $WGET_PROGRESS -O "${temp_file}" "$url" 2>&1
        else
            die "ERROR: Please install wget or curl"
        fi
        echo "$url" > "$download_flag"
    fi
    if [ ! -e "$temp_file" ] ; then
        rm -f "$download_flag"
        die "Failed to download. Retry?"
    fi

    echo "Extracting $temp_file to $dest"
    rm -rf "$dest"
    mkdir -p "$dest"

    case "$temp_file" in
      *".zip") unzip "$temp_file" -d "$dest" ;;
      *) tar -x -f "$temp_file" -C "$dest" ;;
    esac

}

test_valid_down_url() {
    # 参数检查
    if [ -z "$1" ]; then
        echo "Usage: test_valid_down_url <url>"
        return 1
    fi

    local url="$1"
    local status_code

    # 使用curl命令发送HEAD请求并获取状态码
    status_code=$(curl -s -I -L -w '%{http_code}\n' "$url" -o /dev/null)

    # 检查HTTP状态码是否为200
    if [ "$status_code" -eq 200 ]; then
        return 0
    else
        echo "Test url: Failed to download $url : response.status: $status_code"
        return 1
    fi
}

is_cn=$(_is_china && echo true || echo false)

_detect_jdk() {
    local JVM_TARGET_DIR="$JDK_TARGET_DIR/jdk-$JVM_VERSION"
    local flag="jdk.$JVM_VERSION.flag"
    if [ "$uninstall" = "true" ]; then
        _msg info "[uninstall] remove $JVM_TARGET_DIR/$flag"
        _msg info "[uninstall] remove $JVM_TARGET_DIR"
        rm -f "$JVM_TARGET_DIR/$flag"
        rm -rf "$JVM_TARGET_DIR"
        return
    fi
    if [ -e "$JVM_TARGET_DIR/$flag" ] && [ -n "$(ls "$JVM_TARGET_DIR")" ] ; then
        # Everything is up-to-date in $JVM_TARGET_DIR, do nothing
        true
    else
      local JVM_ARCH=$(uname -m)
      local ARCHIVE_NAME="jdk.tar.gz"
      if [ "$darwin" = "true" ]; then
          case $JVM_ARCH in
          x86_64)
              if _is_china && test_valid_down_url "$OSS_URL_JDK_DARWIN_X64" == "true"; then
                JVM_URL="$OSS_URL_JDK_DARWIN_X64"
              else
                JVM_URL="https://download.oracle.com/java/17/archive/${JVM_VERSION}_macos-x64_bin.tar.gz"
              fi
              ;;
          arm64)
              if _is_china && test_valid_down_url "$OSS_URL_JDK_DARWIN_AARCH64" == "true"; then
                JVM_URL="$OSS_URL_JDK_DARWIN_AARCH64"
              else
                JVM_URL="https://download.oracle.com/java/17/archive/${JVM_VERSION}_macos-aarch64_bin.tar.gz"
              fi
              ;;
          *)
              die "Unknown architecture $JVM_ARCH"
              ;;
          esac
      elif [ "$cygwin" = "true" ] || [ "$msys" = "true" ]; then
          if _is_china && test_valid_down_url "$OSS_URL_JDK_WIN_X64" == "true"; then
            JVM_URL="$OSS_URL_JDK_WIN_X64"
          else
            JVM_URL="https://download.oracle.com/java/17/archive/${JVM_VERSION}_windows-x64_bin.zip"
          fi
          ARCHIVE_NAME="jdk.zip"
      else
          JVM_ARCH=$(linux$(getconf LONG_BIT) uname -m)
           case $JVM_ARCH in
              x86_64)
                  if _is_china && test_valid_down_url "$OSS_URL_JDK_LINUX_X64" == "true"; then
                    JVM_URL="$OSS_URL_JDK_LINUX_X64"
                  else
                    JVM_URL="https://download.oracle.com/java/17/archive/${JVM_VERSION}_linux-x64_bin.tar.gz"
                  fi
                  ;;
              aarch64)
                  if _is_china && test_valid_down_url "$OSS_URL_JDK_LINUX_AARCH64" == "true"; then
                    JVM_URL=$OSS_URL_JDK_LINUX_AARCH64
                  else
                    JVM_URL="https://download.oracle.com/java/17/archive/${JVM_VERSION}_linux-aarch64_bin.tar.gz"
                  fi
                  ;;
              *)
                  die "Unknown architecture $JVM_ARCH"
                  ;;
              esac
      fi

      _download_extract "JDK" "$JVM_URL" "$JVM_TARGET_DIR" "$ARCHIVE_NAME"
      echo "$JVM_URL" > "$JVM_TARGET_DIR/$flag"
    fi

    JAVA_HOME=
    for d in "$JVM_TARGET_DIR" "$JVM_TARGET_DIR"/* "$JVM_TARGET_DIR"/Contents/Home "$JVM_TARGET_DIR"/*/Contents/Home; do
      if [ -e "$d/bin/java" ]; then
        JAVA_HOME="$d"
      fi
    done

    if [ '!' -e "$JAVA_HOME/bin/java" ]; then
      rm -f "$JVM_TARGET_DIR/$flag"
      rm -rf "$JVM_TARGET_DIR"
      _msg info "Retry?"
      die "Unable to find bin/java under $JVM_TARGET_DIR"
    fi
    _msg info "[Version] JDK: $JAVA_HOME (version: $JVM_VERSION)"

    # Make it available for child processes
    export JAVA_HOME

    set +e

    # Determine the Java command to use to start the JVM.
    if [ -n "$JAVA_HOME" ] ; then
        if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
            # IBM's JDK on AIX uses strange locations for the executables
            JAVACMD=$JAVA_HOME/jre/sh/java
        else
            JAVACMD=$JAVA_HOME/bin/java
        fi
        if [ ! -x "$JAVACMD" ] ; then
            rm -f "$JVM_TARGET_DIR/$flag"
            rm -rf "$JVM_TARGET_DIR"
            _msg info "Retry?"
            die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME"
        fi
    else
        rm -f "$JVM_TARGET_DIR/$flag"
        rm -rf "$JVM_TARGET_DIR"
        _msg info "Retry?"
        die "ERROR: JAVA_HOME is not set"
    fi

    # Increase the maximum file descriptors if we can.
    if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
        case $MAX_FD in #(
          max*)
            MAX_FD=$( ulimit -H -n ) ||
                warn "Could not query maximum file descriptor limit"
        esac
        case $MAX_FD in  #(
          '' | soft) :;; #(
          *)
            ulimit -n "$MAX_FD" ||
                warn "Could not set maximum file descriptor limit to $MAX_FD"
        esac
    fi

    # For Cygwin or MSYS, switch paths to Windows format before running java
    if "$cygwin" || "$msys" ; then
        APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
        CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )

        JAVACMD=$( cygpath --unix "$JAVACMD" )

        # Now convert the arguments - kludge to limit ourselves to /bin/sh
        for arg do
            if
                case $arg in                                #(
                  -*)   false ;;                            # don't mess with options #(
                  /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
                        [ -e "$t" ] ;;                      #(
                  *)    false ;;
                esac
            then
                arg=$( cygpath --path --ignore --mixed "$arg" )
            fi
            # Roll the args list around exactly as many times as the number of
            # args, so each arg winds up back in the position where it started, but
            # possibly modified.
            #
            # NB: a `for` loop captures its iteration list before it begins, so
            # changing the positional parameters here affects neither the number of
            # iterations, nor the values presented in `arg`.
            shift                   # remove old arg
            set -- "$@" "$arg"      # push replacement arg
        done
    fi
}


_detect_corax() {
    local CJ_TARGET_DIR="$CORAX_TARGET_DIR/corax-$CORAX_VERSION" # CORAX_JAVA TARGET_DIR

    local flag="corax.$CORAX_VERSION.flag"

    if [ "$uninstall" = "true" ]; then
        _msg info "[uninstall] remove $CJ_TARGET_DIR/$flag"
        _msg info "[uninstall] remove $CJ_TARGET_DIR"
        rm -f "$CJ_TARGET_DIR/$flag"
        rm -rf "$CJ_TARGET_DIR"
        return
    fi
    if [ -e "$CJ_TARGET_DIR/$flag" ] && [ -n "$(ls "$CJ_TARGET_DIR")" ]; then
        # Everything is up-to-date in $CJ_TARGET_DIR, do nothing
        true
    else
      local ARCHIVE_NAME=$CORAX_JAVA_ARTIFACT_ZIP
      if _is_china && test_valid_down_url "$OSS_URL_CORAX_JAVA_CLI_COMMUNITY" ; then
          CORAX_JAVA_RELEASE_URL="$OSS_URL_CORAX_JAVA_CLI_COMMUNITY"
      else
          CORAX_JAVA_RELEASE_URL="https://github.com/Feysh-Group/corax-community/releases/download/v$CORAX_VERSION/$CORAX_JAVA_ARTIFACT_ZIP"
      fi

      _download_extract "Corax Java" "$CORAX_JAVA_RELEASE_URL" "$CJ_TARGET_DIR" "$ARCHIVE_NAME"
      echo "$CORAX_JAVA_RELEASE_URL" > "$CJ_TARGET_DIR/$flag"
    fi
    CORAX_HOME=
    CORAX_JAR=
    for d in "$CJ_TARGET_DIR" "$CJ_TARGET_DIR"/*; do
      if [ -e "$d/$CORAX_JAVA_CLI_NAME" ]; then
        CORAX_HOME="$d"
      fi
    done

    _msg info "[Version] Corax Java: $CORAX_HOME (version: $CORAX_VERSION)"

    # Make it available for child processes
    export CORAX_HOME

    set +e

    # Determine the Java command to use to start the JVM.
    if [ -n "$CORAX_HOME" ] ; then
        CORAX_JAR="$CORAX_HOME/$CORAX_JAVA_CLI_NAME"
        if [ ! -f "$CORAX_JAR" ] ; then
            rm -f "$CJ_TARGET_DIR/$flag"
            rm -rf "$CJ_TARGET_DIR"
            _msg info "Retry?"
            die "ERROR: CORAX_HOME is set to an invalid directory: $CORAX_HOME"
        fi
    else
        rm -f "$CJ_TARGET_DIR/$flag"
        rm -rf "$CJ_TARGET_DIR"
        _msg info "Retry?"
        die "ERROR: CORAX_HOME could not found in $CJ_TARGET_DIR."
    fi
}

_delegate_corax_run() {
    set -- \
            -jar "$CORAX_JAR" \
            --enable-data-flow true \
            "$@"

    local config_param_found=false
    for arg in "$@"; do
      if [[ $arg == "--config" ]]; then
        config_param_found=true
        break
      fi
    done
    if [[ $config_param_found == "false" ]]; then
        # 如果没有包含，则添加到参数列表的末尾
            set -- \
                "$@" \
                --config default-config.yml@"$CORAX_HOME"/analysis-config
    fi
    # Stop when "xargs" is not available.
    if ! command -v xargs >/dev/null 2>&1
    then
        die "xargs is not available"
    fi

    eval "set -- $(
            printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS" |
            xargs -n1 |
            sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
            tr '\n' ' '
        )" '"$@"'

    _msg info "[cmd] $JAVACMD $@"
    exec "$JAVACMD" "$@"
}

main() {
    set -e ## 出现错误自动退出
    # set -u ## 变量未定义报错
    SECONDS=0

    ## check OS version/type/install command/install software / 检查系统版本/类型/安装命令/安装软件
    _detect_os
    _detect_jdk
    _detect_corax
    if [ "$uninstall" = "true" ]; then
        _msg info "[uninstall] remove $BUILD_DIR"
        rm -rf "$BUILD_DIR"
        return
    fi
    _delegate_corax_run "$@"
}



[[ $# -eq 1 ]] && [ "$1" == "uninstall" ] && uninstall=true


main "$@"