#!/bin/bash
# shellcheck disable=SC1090
# Various Build utils ##############################################################################################

export RED='\033[0;31m'
export GREEN='\033[0;32m'
export YELLOW='\033[1;33m'
export WHITE='\033[1;37m'
export NC='\033[0m'
export LIGHT_BLUE='\033[38;5;111m'

# retrieve the number of executing processors
export NCPU="$(getconf _NPROCESSORS_ONLN 2> /dev/null)"

# The current path is also configured in TeamCity configuration:
# "settings -> Dependencies -> Artifact Dependencies"
export RESULT_CHANGED_BUILD_PATH_DIR="temp/prev_libs"
export RESULT_CHANGED_BUILD_PATH_FILE="${RESULT_CHANGED_BUILD_PATH_DIR}/res_changed_build_library.txt"

# Prints call stack of function, script path and line where function was called
function get_stack()
{
    echo "Stack trace:"
    # to avoid noise we start with 1 to skip get_stack caller
    local i
    local stack_size=${#FUNCNAME[@]}
    for (( i=1; i<"$stack_size" ; i++ )); do
        local func="${FUNCNAME[$i]}"
        [ x"$func" = x ] && func=MAIN
        local linen="${BASH_LINENO[(( i - 1 ))]}"
        local src="${BASH_SOURCE[$i]}"
        [ x"$src" = x ] && src=non_file_source
        printf "\tFunction: %-20s    File: %s    Line: %s\n" "$func" "$src" "$linen"
    done
}

# Prints text with LIGHT_BLUE marker: [INFO] <text>
function print_info()
{
    echo -e "${LIGHT_BLUE}[INFO]${NC}" "$@"
}

# Prints text with RED marker: [ERROR] <text>
function print_error()
{
    echo -e "${RED}[ERROR]${NC}" "$@" >&2
    get_stack
    exit 1
}

# Prints text with GREEN marker: [SUCCESS] <text>
function print_success()
{
    echo -e "${GREEN}[SUCCESS]${NC}" "$@"
}

# Prints text with YELLOW marker: [WARNING] <text>
function print_warning()
{
    echo -e "${YELLOW}[WARNING]${NC}" "$@"
}

# If 0 - Prints [SUCCESS] label with specified message.
# If !0 - Prints [ERROR] label with specified message.
# Parameters:
#       $1 - return code of operation to check.
#       $2 - Message to print.

# Usage: use $? as $q parameter right after operation that you want to check.
# Use second argument as message to print description.
# Example:
#            ../<some_folder>/some_script.sh <with_some_parameters>
#            check_return_code $? "Some script with some parameters experienced some errors."
#
function check_return_code()
{
    if [ "$1" != "0" ]
    then
        print_error "Code: $1. $2"
        exit "$1"
    else
        print_success "Code: $1. $2"
    fi
}

function contain_element()
{
    local e

    for e in "${@:2}";
    do
        [[ "$e" == "$1" ]] && return 0
    done

    return 1
}

# Pass arguments as strings: check_required "Error message" "$PARAM_TO_CHECK"
function check_required()
{
    if [ "$2" == "" ]; then
        print_error "Message: $1"
        exit 1
    fi
}

function check_existing()
{
    check_arg_num $# 2 "function check_existing()"

    if [ ! -e "$2" ]; then
        print_error "Message: $1 Not existing!"
        exit 1
    fi
}

# Source files
# $1 file to source
# $2 alternative file to source
# source $1 if $1 != "". Otherwise - source $2
# if $1 not "" and not exist on disc, return 1. Otherwise - return 0;
# exit with code 1, if $1 != "" and no defaults exist
function source_file()
{
    check_arg_num $# 2 "function source_file()"

    if [ "$1" != "" ]; then
        if [ -e "$1" ]; then
            print_info "Source: $1"
            source "${1}"
        else
            print_warning "Can not source file: $1"
            return 1
        fi
    else
        if [ -e "$2" ]; then
            print_info "Source default: $2"
            source "${2}"
        else
            print_error "Bad usage. Default file not exist: $2"
            exit 2
        fi
    fi

    return 0
}

# Usage: check_arg_num $# 3 "Expected min 3 arguments in <somewhere>"
function check_arg_num()
{
    if [ "$#" -ne 3 ]; then
        print_error "Bad usage of check_arg_num: $# args, but 3 expected."
        exit 1
    fi

    if [ "$1" -lt "$2" ]; then
        print_error "Illegal number of arguments. Message: $3"
        exit 1
    fi
}

function set_name_for_tz_entity
{
    if [ "$TZ_PROVIDER" == "MOBICORE" ] && [ "$MC_DRIVER" == "TRUE" ]; then
        TZ_ENTITY="_dr"
    elif [ "$TZ_PROVIDER" == "TEEGRIS" ] && [ "$TEEGRIS_SYS" == "TRUE" ]; then
        TZ_ENTITY="_sys"

        if [ "$TEEGRIS_SYS_DEBUG" == "TRUE" ]; then
            TZ_ENTITY="${TZ_ENTITY}_dbg"
        fi
    else
        TZ_ENTITY=""
    fi
}

function get_right_library_name
{
    if [ "$TZ_PROVIDER" == "LINUX" ]; then
        local LIBRARY_EXT="so"
    else
        local LIBRARY_EXT="a"
    fi

    set_name_for_tz_entity

    local LOCAL_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")")
    local VER_HEADER_PATH="$LOCAL_DIR/../src/include/openssl/base.h"
    VERSION_FILE="$LOCAL_DIR/version_info"

    if [ ! -f "$VER_HEADER_PATH" ]; then
        print_error "Version header file '$VER_HEADER_PATH' doesn't exist"
    fi

    local LIBRARY_VER=$(grep FIPS_SCRYPTO_MODULE_VERSION_STR "$VER_HEADER_PATH" | tr  -d '"' | awk '{print $4}')

    echo "#!/bin/bash" > "$VERSION_FILE"
    echo "# This file has been automatically generated." >> "$VERSION_FILE"
    echo "# Do not modify." >> "$VERSION_FILE"
    echo "export VERSION=\"$LIBRARY_VER\"" >> "$VERSION_FILE"

    if [ -z "$LIBRARY_VER" ]; then
        print_error "Version string in '$VER_HEADER_PATH' is empty"
    fi

    if [ "${USED_NAMING_LIBRARY_WITH_CHIPSET_TAG}" == "TRUE" ]; then
        local LIBRARY_PATH_WITH_RIGHT_NAME="${TZ_PROVIDER,,}${TZ_ENTITY}_${CHIPSET,,}_scrypto_v${LIBRARY_VER}_x${MACHINE}_${MODE,,}.${LIBRARY_EXT}"
    else
        local LIBRARY_PATH_WITH_RIGHT_NAME="scrypto_v${LIBRARY_VER}_x${MACHINE}_${NAME_LIBRARY_TAG,,}${TZ_ENTITY}_${MODE,,}.${LIBRARY_EXT}"
    fi

    basename "$LIBRARY_PATH_WITH_RIGHT_NAME"
}

function prepare_toolchains()
{
    if [ "$(echo "$ARM_RVCT_PATH" | grep '/opt/toolchains/ARM-DS5/6')" != "" ] ; then
        # If DS-5 V6 compiler is used
        rm -rf build_links
        mkdir -p build_links/bin

        ln -sf "${ARM_RVCT_PATH}"/bin/* "$(pwd)/build_links/bin/"
        export PATH=$PATH:${ARM_RVCT_PATH}/bin
        ln -sf "${TOOLS_DIR}"/build_env/* "$(pwd)/build_links/bin/"
        export ARM_RVCT_PATH_BIN=""
        ARM_RVCT_PATH_BIN=$(pwd)/build_links/bin/

        print_info "ARM_RVCT_PATH_BIN=$ARM_RVCT_PATH_BIN"
    fi
}

function is_library_with_asm()
{
    [ -e "$1" ] || print_error "The \"$1\" file doesn't exit!"
    return $(ar -t "$1" | grep -qe ".*S\.o$")
}

function is_library_with_custom_libc()
{
    [ -e "$1" ] || print_error "The \"$1\" file doesn't exit!"
    return $(ar -t "$1" | grep -qe "CUSTOM_LIBC_")
}

function is_library_with_no_neon()
{
    return $([[ "$EXTERNAL_LIB_MODULE_CONFIG" == *"module_config_armv4_no_neon"* ]])
}

function get_right_syms_check_list()
{
    function add_config_suffix()
    {
        # Ex:
        # 1) config/additional/crypto_config_generic_asm -> _[generic_asm]
        # 2) ./config/CI/debug_mem_trace_module_config -> ""
        # 3) samples/RSA_Sign_Sample -> _[samples/RSA_Sign_Sample]
        SUFFIX=$(basename "$1" | sed 's/.*_config[_]\?\(.*\)/\1/g')
        if [ "$SUFFIX" != "" ] ; then
            SUFFIX="_[$SUFFIX]"
        fi
        echo $SUFFIX
    }

    set_name_for_tz_entity

    local SYMS_LIST="${TOOLS_DIR}/FIPSCANISTER_syms_check/SWD_FIPSCANISTER_syms_reference_${ROOT_TEE}${TZ_ENTITY}_${CHIPSET}_x${MACHINE}"

    is_library_with_custom_libc "${LIBCRYPTO_LIB_PATH}" \
    && SYMS_LIST="${SYMS_LIST}_[custom_libc]" || true
    is_library_with_asm "$LIBCRYPTO_LIB_PATH" \
    || SYMS_LIST="${SYMS_LIST}_[no_asm]"

    SYMS_LIST="${SYMS_LIST}$(add_config_suffix $EXTERNAL_LIB_MODULE_CONFIG)"
    SYMS_LIST="${SYMS_LIST}$(add_config_suffix $EXTERNAL_LIB_CRYPTO_CONFIG)"
    SYMS_LIST="${SYMS_LIST}$(add_config_suffix $EXTERNAL_LIB_USER_CONFIG)"
    SYMS_LIST="${SYMS_LIST}.list"
    readlink -f ${SYMS_LIST}
}

function check_adb_device()
{
    function more_than_one_device_error()
    {
        print_error "ERROR: \n
        Multiple devices connected but any single device specified. \n
        Export \$adb_device variable to specify a single device serial number."
        adb devices -l
    }

    echo ""
    local DEV_LIST="$(adb devices -l | sed '/^$/d' | grep -ve '* .* *' | grep -v 'List of devices attached')"
    if [ -z "${adb_device}" ]; then
      local DEV_NUM="$(adb devices -l | grep -c ' device ')"
      test "$DEV_NUM" -eq 0 && print_error "List of devices is empty" && return 1
      test "$DEV_NUM" == 1 && export adb_device="-s $(echo ${DEV_LIST} | awk '{print $1}' | tr -d '\n\r')"
      test "$DEV_NUM" -gt 1 && more_than_one_device_error && return 1
    else
      echo ${DEV_LIST} | grep -w ${adb_device} > /dev/null 2>&1
      if [ "$?" != '0' ]; then
        print_error "DEVICE: <${adb_device##-s}> is not present"
        adb devices -l
        return 1
      fi
    fi
    return 0
}

function perforce_preconditions_check ()
{
  print_info "P4 is ${P4}"
  print_info "P4PORT is ${P4PORT}"
  print_info "P4USER is ${P4USER}"
  print_info "P4CLIENT is ${P4CLIENT}"
  print_info "WORKDIR is ${WORKDIR}"

  if [[ -z "${P4}" || -z "${P4CLIENT}" \
      || -z "${P4USER}" || -z "${P4PORT}" \
      || -z "${P4PASSWD}" ]]; then
    print_warning "==> Error! One of the mandatory options is empty!">&2
    return 1
  fi
  return 0
}

function update_internal_version_header ()
{
    set +e

    local LOCAL_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")")
    local HEADER_INTERNAL_VERSION="${LOCAL_DIR}/../src/include/openssl/scrypto_version.h"
    local TMP_INT_VER_FILE="${LOCAL_DIR}/../internal_version.txt"

    if [ "$IS_ENGINEERING_BUILD" == "TRUE" ]; then
        local VALUE_INTERNAL_VERSION="SCRYPTO_ENGINEERING_BUILD: "

        if [ "$SCRYPTO_DEBUG" == "TRUE" ]; then
            VALUE_INTERNAL_VERSION+="[DEBUG]"
        else
            VALUE_INTERNAL_VERSION+="[RELEASE]"
        fi
    else
        if [ "$IS_EMERGENCY_RELEASE_BUILD" == "TRUE" ]; then
            perforce_preconditions_check || return 1
        fi

        ${TOOLS_DIR}/get_build_info.sh "${TMP_INT_VER_FILE}" "$P4" "$P4CLIENT" "$P4PORT" "$P4USER" "$P4PASSWD" || return 1
        local VALUE_INTERNAL_VERSION=$(grep -m 1 -e '^Change ' ${TMP_INT_VER_FILE} | awk '{print $2}')
        rm ${TMP_INT_VER_FILE}

        if [ "${VALUE_INTERNAL_VERSION}" == "" ]; then
            print_warning "Could not get value of internal version from current CL."
            return 1
        fi
    fi

    local VALUE_DEFINE_HEADER="FIPS_SCRYPTO_INTERNAL_VERSION_STR"
    VALUE_INTERNAL_VERSION="CL ${VALUE_INTERNAL_VERSION}"

    if [ "$(grep -oP ${VALUE_DEFINE_HEADER}' "\K[^"]+' ${HEADER_INTERNAL_VERSION})" != "${VALUE_INTERNAL_VERSION}" ]; then
        echo "Update header of internal version..."
        sed -i "s/\(${VALUE_DEFINE_HEADER} \).*/\1\"${VALUE_INTERNAL_VERSION}\"/" "${HEADER_INTERNAL_VERSION}"
    fi

    echo "-- current value: ${VALUE_INTERNAL_VERSION}"

    set -e
}

# Function for checking changes in binary library between previous and current build
# Performed a binary comparison excluding information about the internal version of the library.
function check_for_changed_build ()
{
    [[ "$IS_ENGINEERING_BUILD" == "TRUE" || "$IS_EMERGENCY_RELEASE_BUILD" == "TRUE" ]] && return 0

    local LOCAL_DIR=$(dirname "$(readlink -f "$BASH_SOURCE")")
    local ROOT_DIR="${LOCAL_DIR}/.."
    local OBJECT_SCRYPTO_VERSION="scrypto_version.c.o"
    local FLAG_FOR_DETERMINISTIC_ARCH="--enable-deterministic-archives"
    local CURRENT_LIB_NAME=$(get_right_library_name)
    local PATH_CURRENT_LIB="$(find -L "${ROOT_DIR}/samples/scrypto" -name "${CURRENT_LIB_NAME}")"
    local PATH_PREVIOUS_LIB_COMPARE="${ROOT_DIR}/${RESULT_CHANGED_BUILD_PATH_DIR}/${CURRENT_LIB_NAME}"
    local PATH_CURRENT_LIB_COMPARE="${PATH_PREVIOUS_LIB_COMPARE}.new.a"
    local PATH_RESULT_FILE="${ROOT_DIR}/${RESULT_CHANGED_BUILD_PATH_FILE}"
    local IS_CHANGES_BUILD="CHANGED"

    set +e

    [[ ! -e "${PATH_RESULT_FILE}" ]] && mkdir -p "${ROOT_DIR}/${RESULT_CHANGED_BUILD_PATH_DIR}" && touch "${PATH_RESULT_FILE}"

    # check for the existence of binary from previous build
    if [ -e "${PATH_PREVIOUS_LIB_COMPARE}" ]; then

        # copy current binary in directory for comparison
        chmod 0666 ${PATH_PREVIOUS_LIB_COMPARE}
        cp -f "${PATH_CURRENT_LIB}" "${PATH_CURRENT_LIB_COMPARE}"

        # remove members with version info from binary (previous and current)
        "${AR}" "${AR_O_DELETEMEMBERS}" "${PATH_CURRENT_LIB_COMPARE}" "${OBJECT_SCRYPTO_VERSION}"
        "${AR}" "${AR_O_DELETEMEMBERS}" "${PATH_PREVIOUS_LIB_COMPARE}" "${OBJECT_SCRYPTO_VERSION}"

        # strip both binary to remove additional archive info
        "${STRIP}" "${FLAG_FOR_DETERMINISTIC_ARCH}" "${PATH_CURRENT_LIB_COMPARE}"
        "${STRIP}" "${FLAG_FOR_DETERMINISTIC_ARCH}" "${PATH_PREVIOUS_LIB_COMPARE}"

        # compare previous and current binary
        # if the binaries are the same then mark as "not changed"
        if [ -z "$(diff "${PATH_CURRENT_LIB_COMPARE}" "${PATH_PREVIOUS_LIB_COMPARE}")" ]; then
            IS_CHANGES_BUILD="unchanged"
        fi
    fi

    # save result of comparison in the report file
    echo "${CURRENT_LIB_NAME} ${IS_CHANGES_BUILD}" >> "${PATH_RESULT_FILE}"

    set -e
    print_info "Built library ${CURRENT_LIB_NAME} is ${IS_CHANGES_BUILD}."
}

function get_device_chipname()
{
    adb ${adb_device} shell getprop | grep chipname | cut -d ':' -f2 | tr -d '[:space:]\[\]' | tr '[:upper:]' '[:lower:]'
}

function get_remount_device_point_qc()
{
    #default value for QC
    local res="/firmware"
    local chipname="$(get_device_chipname)"

    if [ "${chipname}" == "sm8150" ]; then
        res="/vendor/firmware_mnt"
    fi

    echo "$res"
}

function get_remount_device_point_mc()
{
    #default value for MC
    local res="/vendor"

    echo "$res"
}

function get_remount_device_point_tg()
{
    #default value for TG
    local res="/system"
    local destination="${1}"

    if [ "${destination}" == "device" ]; then
        res="/vendor"
    elif [ "${destination}" == "qemu" ]; then
        res="/system"
    fi

    echo "$res"
}

function get_remount_device_point()
{
    local platform="${1}"
    local destination="${2}"

    echo "$(get_remount_device_point_${platform} ${destination})"
}

# Export functions
export -f get_stack
export -f print_info
export -f print_error
export -f print_success
export -f print_warning
export -f check_return_code
export -f contain_element
export -f check_required
export -f check_existing
export -f source_file
export -f check_arg_num
export -f get_right_library_name
export -f prepare_toolchains
export -f is_library_with_asm
export -f is_library_with_custom_libc
export -f is_library_with_no_neon
export -f get_right_syms_check_list
export -f check_adb_device
export -f perforce_preconditions_check
export -f update_internal_version_header
export -f check_for_changed_build
export -f get_device_chipname
export -f get_remount_device_point_qc
export -f get_remount_device_point_mc
export -f get_remount_device_point_tg
export -f get_remount_device_point
