#
# Copyright (c) 2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#

#
# Functions related to build avoidance.
#
# Do not call directly.  Used by build-pkgs.
#
# Build avoidance downloads rpm, src.rpm and other artifacts of
# build-pkgs for a local reference build.  The reference would
# typically be an automated build run atleast daily.
# The MY_WORKSPACE directory for the reference build shall have
# a common root directory, and a leaf directory that is a time stamp
# in a sortable parsable format.   Default YYYYMMDDThhmmssZ.
#  e.g. /localdisk/loadbuild/jenkins/StarlingX/20180719T113021Z
#
# Other formats can be used by setting the following variables
# in $MY_REPO/local-build-data/build_avoidance_source.
#   e.g. to allow format YYYY-MM-DD_hh-mm-ss
# BUILD_AVOIDANCE_DATE_FORMAT="%Y-%m-%d"
# BUILD_AVOIDANCE_TIME_FORMAT="%H-%M-%S"
# BUILD_AVOIDANCE_DATE_TIME_DELIM="_"
# BUILD_AVOIDANCE_DATE_TIME_POSTFIX=""
#
# Note: Must be able to rsync and ssh to the machine that holds the
# reference builds.
#
# In future alternative transfer protocols may be supported.
# Select the alternate protocol by setting the following variables
# in $MY_REPO/local-build-data/build_avoidance_source.
# e.g.
# BUILD_AVOIDANCE_FILE_TRANSFER="my-supported-prototcol"
#

BUILD_AVOIDANCE_USR=""
BUILD_AVOIDANCE_HOST=""
BUILD_AVOIDANCE_DIR=""
BUILD_AVOIDANCE_URL=""

# Default date/time format, iso-8601 compact, 20180912T143913Z
# Syntax is a subset of that use by the unix 'date' command.
BUILD_AVOIDANCE_DATE_FORMAT="%Y%m%d"
BUILD_AVOIDANCE_TIME_FORMAT="%H%M%S"
BUILD_AVOIDANCE_DATE_TIME_DELIM="T"
BUILD_AVOIDANCE_DATE_TIME_POSTFIX="Z"

# Default file transfer method
BUILD_AVOIDANCE_FILE_TRANSFER="rsync"

# Default is to use timestamps and days in UTC
#
# If you prefer local time, then set 'BUILD_AVOIDANCE_DATE_UTC=0'
# in '$MY_REPO/local-build-data/build_avoidance_source'
BUILD_AVOIDANCE_DATE_UTC=1

BUILD_AVOIDANCE_DATA_DIR="$MY_WORKSPACE/build_avoidance_data"
BUILD_AVOIDANCE_SOURCE="$MY_REPO/build-data/build_avoidance_source"
BUILD_AVOIDANCE_LOCAL_SOURCE="$MY_REPO/local-build-data/build_avoidance_source"
BUILD_AVOIDANCE_TEST_CONTEXT="$BUILD_AVOIDANCE_DATA_DIR/test_context"
BUILD_AVOIDANCE_LAST_SYNC_FILE="$BUILD_AVOIDANCE_DATA_DIR/last_sync_context"

if [ ! -f $BUILD_AVOIDANCE_SOURCE ]; then
    echo "Couldn't read $BUILD_AVOIDANCE_SOURCE"
    exit 1
fi

echo "Reading: $BUILD_AVOIDANCE_SOURCE"
source $BUILD_AVOIDANCE_SOURCE

if [ -f $BUILD_AVOIDANCE_LOCAL_SOURCE ]; then
    echo "Reading: $BUILD_AVOIDANCE_LOCAL_SOURCE"
    source $BUILD_AVOIDANCE_LOCAL_SOURCE
fi

UTC=""

if [ $BUILD_AVOIDANCE_DATE_UTC -eq 1 ]; then
    UTC="--utc"
fi


if [ "x$BUILD_AVOIDANCE_OVERRIDE_DIR" != "x" ]; then
    BUILD_AVOIDANCE_DIR="$BUILD_AVOIDANCE_OVERRIDE_DIR"
fi

if [ "x$BUILD_AVOIDANCE_OVERRIDE_HOST" != "x" ]; then
    BUILD_AVOIDANCE_HOST="$BUILD_AVOIDANCE_OVERRIDE_HOST"
fi

if [ "x$BUILD_AVOIDANCE_OVERRIDE_USR" != "x" ]; then
    BUILD_AVOIDANCE_USR="$BUILD_AVOIDANCE_OVERRIDE_USR"
fi

echo "BUILD_AVOIDANCE_DIR=$BUILD_AVOIDANCE_DIR"
echo "BUILD_AVOIDANCE_HOST=$BUILD_AVOIDANCE_HOST"
echo "BUILD_AVOIDANCE_USR=$BUILD_AVOIDANCE_USR"

build_avoidance_clean () {
    if [ -f $BUILD_AVOIDANCE_LAST_SYNC_FILE ]; then
        \rm -f -v "$BUILD_AVOIDANCE_LAST_SYNC_FILE"
    fi
}


date_to_iso_8601 () {
    local DATE="$1"
    local CENTURY=""
    local YEAR_IN_CENTURY="00"
    local MONTH="01"
    local DAY="01"
    local DAY_OF_YEAR=""

    CENTURY="$(date  '+%C')"

    for x in $(echo "${BUILD_AVOIDANCE_DATE_FORMAT}" | tr ' ' '#' | sed 's/%%/#/g' | tr '%' ' ' ); do
        # Consume format case options
        case ${x:0:1} in
            ^) x=${x:1};;
            \#) x=${x:1};;
            *) ;;
        esac

        # Process format
        case $x in
            Y*)  CENTURY=${DATE:0:2}; YEAR_IN_CENTURY=${DATE:2:2}; DATE=${DATE:4}; x=${x:1};;
            0Y*) CENTURY=${DATE:0:2}; YEAR_IN_CENTURY=${DATE:2:2}; DATE=${DATE:4}; x=${x:2};;
            _Y*) CENTURY=$(echo "${DATE:0:2}" | tr ' ' '0'); YEAR_IN_CENTURY=${DATE:2:2}; DATE=${DATE:4}; x=${x:2};;

            y*)  YEAR_IN_CENTURY=${DATE:0:2}; DATE=${DATE:2}; x=${x:1};;
            0y*) YEAR_IN_CENTURY=${DATE:0:2}; DATE=${DATE:2}; x=${x:2};;
            _y*) YEAR_IN_CENTURY=$(echo "${DATE:0:2}" | tr ' ' '0'); DATE=${DATE:2}; x=${x:2};;

            C*)  CENTURY=${DATE:0:2}; DATE=${DATE:2}; x=${x:1};;
            0C*) CENTURY=${DATE:0:2}; DATE=${DATE:2}; x=${x:2};;
            _C*) CENTURY=$(echo "${DATE:0:2}" | tr ' ' '0'); DATE=${DATE:2}; x=${x:2};;

            m*)  MONTH=${DATE:0:2}; DATE=${DATE:2}; x=${x:1};;
            0m*) MONTH=${DATE:0:2}; DATE=${DATE:2}; x=${x:2};;
            _m*) MONTH=$(echo "${DATE:0:2}" | tr ' ' '0'); DATE=${DATE:2}; x=${x:2};;
            e*)  MONTH=$(echo "${DATE:0:2}" | tr ' ' '0'); DATE=${DATE:2}; x=${x:1};;
            0e*) MONTH=${DATE:0:2}; DATE=${DATE:2}; x=${x:2};;
            _e*) MONTH=$(echo "${DATE:0:2}" | tr ' ' '0'); DATE=${DATE:2}; x=${x:2};;
            b*)  MONTH="$(date -d "${DATE:0:3} 1 2000" '+%m')"; DATE=${DATE:3}; x=${x:1};;
            h*)  MONTH="$(date -d "${DATE:0:3} 1 2000" '+%m')"; DATE=${DATE:3}; x=${x:1};;

            d*)  DAY=${DATE:0:2}; DATE=${DATE:2}; x=${x:1};;
            0d*) DAY=${DATE:0:2}; DATE=${DATE:2}; x=${x:2};;
            _d*) DAY=$(echo "${DATE:0:2}" | tr ' ' '0'); DATE=${DATE:2}; x=${x:2};;

            j*)  DAY_OF_YEAR=${DATE:0:3}; DATE=${DATE:3}; x=${x:1};;
            0j*) DAY_OF_YEAR=${DATE:0:3}; DATE=${DATE:3}; x=${x:2};;
            _j*) DAY_OF_YEAR=$(echo "${DATE:0:3}" | tr ' ' '0'); DATE=${DATE:3}; x=${x:2};;

            D*) MONTH=${DATE:0:2}; DAY=${DATE:3:2}; YEAR_IN_CENTURY=${DATE:6:2}; DATE=${DATE:8}; x=${x:1};;
            F*) CENTURY=${DATE:0:2}; YEAR_IN_CENTURY=${DATE:2:2}; MONTH=${DATE:5:2}; DAY=${DATE:8:2}; DATE=${DATE:10}; x=${x:1};;
            *) >&2 echo "$FUNCNAME (${LINENO}): Unsupported date format: ${BUILD_AVOIDANCE_DATE_FORMAT}"; return 1;;
        esac

        # consume remaing non-interpreted content
        if [ "$(echo "${DATE:0:${#x}}" |  tr ' ' '#')" != "${x}" ]; then
            >&2 echo "$FUNCNAME (${LINENO}): Unexpected content '${DATE:0:${#x}}' does not match expected '${x}': '$1' being parsed vs '${BUILD_AVOIDANCE_DATE_FORMAT}'"
            return 1
        fi
        DATE=${DATE:${#x}}
    done

    if [ "${DAY_OF_YEAR}" != "" ]; then
        local YEAR_SEC
        local DOY_SEC
        YEAR_SEC="$(date -d "${CENTURY}${YEAR_IN_CENTURY}-01-01" '+%s')"
        DOY_SEC=$((YEAR_SEC+(DAY_OF_YEAR-1)*24*60*60))
        MONTH="$(date "@$DOY_SEC" "+%m")"
        DAY="$(date "@$DOY_SEC" "+%d")"
    fi

    echo "${CENTURY}${YEAR_IN_CENTURY}-${MONTH}-${DAY}"
    return 0
}

time_to_iso_8601 () {
    TIME="$1"
    local HOUR="00"
    local H12=""
    local AMPM=""
    local MINUTE="00"
    local SECOND="00"

    CENTURY="$(date  '+%C')"

    for x in $(echo "${BUILD_AVOIDANCE_TIME_FORMAT}" | tr ' ' '#' | sed 's/%%/#/g' | tr '%' ' ' ); do
        # Consume format case options
        case ${x:0:1} in
            ^) x=${x:1};;
            \#) x=${x:1};;
            *) ;;
        esac

        # Process format
        case $x in
            H*)  HOUR=${TIME:0:2}; TIME=${TIME:2}; x=${x:1};;
            0H*) HOUR=${TIME:0:2}; TIME=${TIME:2}; x=${x:2};;
            _H*) HOUR="$(echo "${TIME:0:2}" | tr ' ' '0')"; TIME=${TIME:2}; x=${x:2};;
            k*)  HOUR="$(echo "${TIME:0:2}" | tr ' ' '0')"; TIME=${TIME:2}; x=${x:1};;
            0k*) HOUR=${TIME:0:2}; TIME=${TIME:2}; x=${x:2};;
            _k*) HOUR="$(echo "${TIME:0:2}" | tr ' ' '0')"; TIME=${TIME:2}; x=${x:2};;

            I*)  H12=${TIME:0:2}; TIME=${TIME:2}; x=${x:1};;
            0I*) H12=${TIME:0:2}; TIME=${TIME:2}; x=${x:2};;
            _I*) H12="$(echo "${TIME:0:2}" | tr ' ' '0')"; TIME=${TIME:2}; x=${x:2};;
            l*)  H12="$(echo "${TIME:0:2}" | tr ' ' '0')"; TIME=${TIME:2}; x=${x:1};;
            0l*) H12=${TIME:0:2}; TIME=${TIME:2}; x=${x:2};;
            _l*) H12="$(echo "${TIME:0:2}" | tr ' ' '0')"; TIME=${TIME:2}; x=${x:2};;
            p*) AMPM=${TIME:0:2}; TIME=${TIME:2}; x=${x:1};;

            M*)  MINUTE=${TIME:0:2}; TIME=${TIME:2}; x=${x:1};;
            0M*) MINUTE=${TIME:0:2}; TIME=${TIME:2}; x=${x:2};;
            _M*) MINUTE="$(echo "${TIME:0:2}" | tr ' ' '0')"; TIME=${TIME:2}; x=${x:2};;

            S*)  SECOND=${TIME:0:2}; TIME=${TIME:2}; x=${x:1};;
            0S*) SECOND=${TIME:0:2}; TIME=${TIME:2}; x=${x:2};;
            _S*) SECOND="$(echo "${TIME:0:2}" | tr ' ' '0')"; TIME=${TIME:2}; x=${x:2};;

            R*) HOUR=${TIME:0:2}; MINUTE=${TIME:3:2} TIME=${TIME:5}; x=${x:1};;
            r*) H12=${TIME:0:2}; MINUTE=${TIME:3:2}; SECOND=${TIME:6:2}; AMPM=${TIME:9:2}; TIME=${TIME:11}; x=${x:1};;
            T*) HOUR=${TIME:0:2}; MINUTE=${TIME:3:2}; SECOND=${TIME:6:2}; TIME=${TIME:8}; x=${x:1};;

            *) >&2 echo "$FUNCNAME (${LINENO}): Unsupported time format: ${BUILD_AVOIDANCE_TIME_FORMAT}"; return 1;;
        esac

        # consume remaing non-interpreted content
        if [ "$(echo "${TIME:0:${#x}}" |  tr ' ' '#')" != "${x}" ]; then
            >&2 echo "$FUNCNAME (${LINENO}): Unexpected content '${TIME:0:${#x}}' does not match expected '${x}': '$1' being parsed vs '${BUILD_AVOIDANCE_TIME_FORMAT}'"
            return 1
        fi
        TIME=${TIME:${#x}}
    done

    if [ "$H12" != "" ] && [ "$AMPM" != "" ]; then
        HOUR="$(date "$H12:01:01 $AMPM" '+%H')"
    else
        if [ "$H12" != "" ] && [ "$AMPM" != "" ]; then
            >&2 echo "$FUNCNAME (${LINENO}): Unsupported time format: ${BUILD_AVOIDANCE_TIME_FORMAT}"
            return 1
        fi
    fi

    echo "${HOUR}:${MINUTE}:${SECOND}"
    return 0
}

date_time_to_iso_8601 () {
    local DATE_TIME="$1"
    local DATE
    local TIME
    local DECODED_DATE
    local DECODED_TIME
    DATE=$(echo "${DATE_TIME}" | cut -d ${BUILD_AVOIDANCE_DATE_TIME_DELIM} -f 1)
    TIME=$(echo "${DATE_TIME}" | cut -d ${BUILD_AVOIDANCE_DATE_TIME_DELIM} -f 2 | sed "s#${BUILD_AVOIDANCE_DATE_TIME_POSTFIX}\$##")
    DECODED_DATE=$(date_to_iso_8601 "${DATE}")
    DECODED_TIME=$(time_to_iso_8601 "${TIME}")
    echo "${DECODED_DATE}T${DECODED_TIME}$(date $UTC '+%:z')"
}

#
# test_build_avoidance_context <path-to-context-file>
#
# Is the provided context file compatible with the current
# state of all of our gits?  A compatible context is one
# where every commit in the context file is visible in our
# current git history.
#
# Returns: Timestamp of context tested.
# Exit code: 0 = Compatible
#            1 = This context is older than the last applied
#                build avoidance context.  If you are searching
#                newest to oldest, you might as well stop.
#            2 = Not compatible
#
test_build_avoidance_context () {
    local context="$1"
    local BA_LAST_SYNC_CONTEXT="$2"
    local BA_CONTEXT=""

    BA_CONTEXT=$(basename $context | cut -d '.' -f 1)
    >&2 echo "test: $BA_CONTEXT"

    if [ "$BA_CONTEXT" == "$BA_LAST_SYNC_CONTEXT" ]; then
        # Stop the search.  We've reached the last sync point
        BA_CONTEXT=""
        echo "$BA_CONTEXT"
        return 1
    fi

    git_test_context "$context"
    result=$?
    if [ $result -eq 0 ]; then
        # found a new context !!!
        echo "$BA_CONTEXT"
        return 0
    fi

    # Continue the search
    BA_CONTEXT=""
    echo "$BA_CONTEXT"
    return 2
}


#
# get_build_avoidance_context
#
# Return URL of the most recent jenkins build that is compatable with
# the current software context under $MY_REPO.
#
get_build_avoidance_context () {
    (
    local context
    local BA_CONTEXT=""
    local BA_LAST_SYNC_CONTEXT=""

    # Load last synced context
    if [ -f $BUILD_AVOIDANCE_LAST_SYNC_FILE ]; then
        BA_LAST_SYNC_CONTEXT=$(head -n 1 $BUILD_AVOIDANCE_LAST_SYNC_FILE)
    fi

    mkdir -p $BUILD_AVOIDANCE_DATA_DIR
    if [ $? -ne 0 ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): mkdir -p $BUILD_AVOIDANCE_DATA_DIR"
        return 1
    fi

    local REMOTE_CTX_DIR="context"
    local LOCAL_CTX_DIR="$BUILD_AVOIDANCE_DATA_DIR/context"

    # First copy the directory containing all the context files for
    # the reference builds.
    >&2 echo "Download latest reference build contexts"

    # Must set this prior to build_avoidance_copy_dir.
    # The setting is not exported outside of the subshell.
    BUILD_AVOIDANCE_URL="$BUILD_AVOIDANCE_HOST:$BUILD_AVOIDANCE_DIR"

    build_avoidance_copy_dir "$REMOTE_CTX_DIR" "$LOCAL_CTX_DIR"
    if [ $? -ne 0 ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): build_avoidance_copy_dir '$REMOTE_CTX_DIR' '$LOCAL_CTX_DIR'"
        return 1
    fi

    # Search for a new context to sync
    cd $MY_REPO

    if [ "$BUILD_AVOIDANCE_DAY" == "" ]; then
        # Normal case:
        # Search all contexts, newest to oldest, for a good context.
        for context in $(ls -1rd $LOCAL_CTX_DIR/*context); do
            >&2 echo "context=$context"
            BA_CONTEXT=$(test_build_avoidance_context $context $BA_LAST_SYNC_CONTEXT)
            if [ $? -le 1 ]; then
                # Stop search.  Might or might not have found a good context.
                break;
            fi
        done
    else
        # Special case when a target day is specified.  Why would we do this?
        # Reason is we might want the reference build to itself use build
        # avoidance referencing prior builds of itself, except for one build
        # a week when we use a full build rather than a build avoidance build.
        #    e.g.   Sunday - full build
        #           Mon-Sat - avoidance builds that refernce Sunday build.
        #
        # Starting from last <TARG_DAY> (e.g. "Sunday"), search newest to
        # oldest for a good context.  If none found, increment the target
        # day (e.g. Monday) and search again.  Keep incrementing until a
        # good build is found, or target day + offset days would be a date
        # in the furure.
        #
        local TARG_DAY=$BUILD_AVOIDANCE_DAY
        local TODAY_DATE
        local TODAY_DAY
        local TARG_DATE=""
        local TARG_TS
        local TODAY_TS

        TODAY_DATE=$(date  $UTC +%Y-%m-%d)
        TODAY_DAY=$(date $UTC "+%A")

        for OFFSET_DAYS in 0 1 2 3 4 5 6; do
            if [ "$TARG_DAY" != "" ]; then
                # Convert TARG_DAY+OFFSET_DAYS to TARG_DATE

                if [ "$TODAY_DAY" == "$TARG_DAY" ]; then
                    TARG_DATE=$(date $UTC -d"$TARG_DAY+$OFFSET_DAYS days" +%Y-%m-%d)
                else
                    TARG_DATE=$(date $UTC -d"last-$TARG_DAY+$OFFSET_DAYS days" +%Y-%m-%d)
                fi
                >&2 echo "TARG_DATE=$TARG_DATE"

                TARG_TS=$(date $UTC -d "$TARG_DATE" +%s)
                TODAY_TS=$(date $UTC -d "$TODAY_DATE" +%s)
                if [ $TARG_TS -gt $TODAY_TS ]; then
                    # Skip if offset has pushed us into future dates
                    continue;
                fi

                if [ "$TARG_DATE" == "$TODAY_DATE" ]; then
                    TARG_DATE=""
                fi
            fi

            # Search build, newest to oldest, satisfying TARG_DATE
            for f in $(ls -1rd $LOCAL_CTX_DIR/*context); do
                DATE=$(date_to_iso_8601 $(basename "$f"))
                if [ $? -ne 0 ]; then
                    >&2 echo "Failed to extract date from filename '$(basename "$f")', ignoring file"
                    continue
                fi

                >&2 echo "   DATE=$DATE, TARG_DATE=$TARG_DATE"

                if [ "$DATE" == "$TARG_DATE" ] || [ "$TARG_DATE" == "" ] ; then
                    context=$f;
                else
                    continue
                fi

                >&2 echo "context=$context"

                BA_CONTEXT=$(test_build_avoidance_context $context $BA_LAST_SYNC_CONTEXT)

                if [ $? -le 1 ]; then
                    # Stop search.  Might or might not have found a good context.
                    break;
                fi
            done

            if [ "$BA_CONTEXT" != "" ]; then
                # Found a good context.
                break
            fi
        done
    fi

    if [ "$BA_CONTEXT" == "" ]; then
        # No new context found
        return 1
    fi

    # test that the reference build context hasn't been deleted
    local BA_CONTEXT_DIR="$BUILD_AVOIDANCE_DIR/$BA_CONTEXT"
    >&2 echo "ssh $BUILD_AVOIDANCE_HOST '[ -d $BA_CONTEXT_DIR ]'"
    if ! ssh $BUILD_AVOIDANCE_HOST '[ -d $BA_CONTEXT_DIR ]' ; then
        return 1
    fi

    # Save the latest context
    >&2 echo "BA_CONTEXT=$BA_CONTEXT"
    >&2 echo "BUILD_AVOIDANCE_LAST_SYNC_FILE=$BUILD_AVOIDANCE_LAST_SYNC_FILE"
    echo $BA_CONTEXT > $BUILD_AVOIDANCE_LAST_SYNC_FILE

    # The location of the load with the most compatable new context
    URL=$BUILD_AVOIDANCE_HOST:$BA_CONTEXT_DIR

    # return URL to caller.
    echo $URL
    return 0
    )
}


#
# build_avoidance_pre_clean <build-type>
#
# A place for any cleanup actions that must preceed a build avoidance build.
#
build_avoidance_pre_clean () {
    local BUILD_TYPE="$1"

    if [ "$BUILD_TYPE" == "" ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): BUILD_TYPE required"
        return 1
    fi

    # clean prior builds
    if [ -d $MY_WORKSPACE/$BUILD_TYPE ]; then
        build-pkgs --clean --$BUILD_TYPE --no-build-avoidance
        if [ $? -ne 0 ]; then
            return 1
        fi
    fi

    for f in $BUILD_AVOIDANCE_SRPM_FILES $BUILD_AVOIDANCE_RPM_FILES; do
        if [ -f $MY_WORKSPACE/$BUILD_TYPE/$f ]; then
            \rm -f $MY_WORKSPACE/$BUILD_TYPE/$f
            if [ $? -ne 0 ]; then
                >&2 echo "Error: $FUNCNAME (${LINENO}): rm -f $MY_WORKSPACE/$BUILD_TYPE/$f"
                return 1
            fi
        fi
    done

    for d in $BUILD_AVOIDANCE_SRPM_DIRECTORIES $BUILD_AVOIDANCE_RPM_DIRECTORIES; do

        if [ -d $MY_WORKSPACE/$BUILD_TYPE/$d ]; then
            \rm -rf $MY_WORKSPACE/$BUILD_TYPE/$d
            if [ $? -ne 0 ]; then
                >&2 echo "Error: $FUNCNAME (${LINENO}): rm -rf $MY_WORKSPACE/$BUILD_TYPE/$d"
                return 1
            fi
        fi
    done

    return 0
}


#
# build_avoidance_copy_dir_rsync <remote-dir-path-rel> <local-dir-path> ['verbose']
#
# Copy a file from $BUILD_AVOIDANCE_URL/<remote-dir-path-rel>
# to <local-dir-path> using rsync.
#
build_avoidance_copy_dir_rsync () {
    local FROM="$1"
    local TO="$2"
    local VERBOSE="$3"
    local FLAGS="-a -u"

    if [ "$BUILD_AVOIDANCE_URL" == "" ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): BUILD_AVOIDANCE_URL no set"
        return 1
    fi
    if [ "$VERBOSE" != "" ]; then
        FLAGS="$FLAGS -v"
        echo "rsync $FLAGS '$BUILD_AVOIDANCE_URL/$FROM/*' '$TO/'"
    fi
    rsync $FLAGS "$BUILD_AVOIDANCE_URL/$FROM/*" "$TO/"
    return $?
}

#
# build_avoidance_copy_file_rsync <remote-file-path-rel> <local-file-path> ['verbose']
#
# Copy a file from $BUILD_AVOIDANCE_URL/<remote-file-path-rel>
# to <local-file-path> using rsync.
#
build_avoidance_copy_file_rsync () {
    local FROM="$1"
    local TO="$2"
    local VERBOSE="$3"
    local FLAGS="-a -u"

    if [ "$BUILD_AVOIDANCE_URL" == "" ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): BUILD_AVOIDANCE_URL no set"
        return 1
    fi
    if [ "$VERBOSE" != "" ]; then
        FLAGS="$FLAGS -v"
        echo "rsync $FLAGS '$BUILD_AVOIDANCE_URL/$FROM' '$TO'"
    fi
    rsync $FLAGS "$BUILD_AVOIDANCE_URL/$FROM" "$TO"
    return $?
}

#
# build_avoidance_copy_dir <remote-dir-path-rel> <local-dir-path> ['verbose']
#
# Copy a file from $BUILD_AVOIDANCE_URL/<remote-dir-path-rel>
# to <local-dir-path>.  The copy method will be determined by
# BUILD_AVOIDANCE_FILE_TRANSFER.  Only 'rsync' is supported at present.
#
# <local-dir-path> should be a directory,
# mkdir -p will be called on <local-file-path>.
#
build_avoidance_copy_dir () {
    local FROM="$1"
    local TO="$2"
    local VERBOSE="$3"

    if [ "$VERBOSE" != "" ]; then
        echo "mkdir -p '$TO'"
    fi
    mkdir -p "$TO"
    if [ $? -ne 0 ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): mkdir -p $TO"
        return 1
    fi

    case ${BUILD_AVOIDANCE_FILE_TRANSFER} in
        rsync)
            build_avoidance_copy_dir_rsync "$FROM" "$TO" "$VERBOSE"
            return $?
            ;;
        *)
            >&2 echo "Error: $FUNCNAME (${LINENO}): Unknown BUILD_AVOIDANCE_FILE_TRANSFER '${BUILD_AVOIDANCE_FILE_TRANSFER}'"
            return 1
            ;;
    esac
    return 1
}

#
# build_avoidance_copy_file <remote-file-path-rel> <local-file-path> ['verbose']
#
# Copy a file from $BUILD_AVOIDANCE_URL/<remote-file-path-rel>
# to <local-file-path>.  The copy method will be determined by
# BUILD_AVOIDANCE_FILE_TRANSFER.  Only 'rsync' is supported at present.
#
# <local-file-path> should be a file, not a directory,
# mkdir -p will be called on $(dirname <local-file-path>)
#
build_avoidance_copy_file () {
    local FROM="$1"
    local TO="$2"
    local VERBOSE="$3"

    if [ "$VERBOSE" != "" ]; then
        echo "mkdir -p $(dirname '$TO')"
    fi
    mkdir -p "$(dirname "$TO")"
    if [ $? -ne 0 ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): mkdir -p $(dirname "$TO")"
        return 1
    fi

    case ${BUILD_AVOIDANCE_FILE_TRANSFER} in
        rsync)
            build_avoidance_copy_file_rsync "$FROM" "$TO" "$VERBOSE"
            return $?
            ;;
        *)
            >&2 echo "Error: $FUNCNAME (${LINENO}): Unknown BUILD_AVOIDANCE_FILE_TRANSFER '${BUILD_AVOIDANCE_FILE_TRANSFER}'"
            return 1
            ;;
    esac
    return 1
}

#
# build_avoidance_copy <build-type> ['verbose']
#
# Copy the needed build artifacts for <build-type> from $BUILD_AVOIDANCE_URL.
#
build_avoidance_copy () {
    local BUILD_TYPE="$1"
    local VERBOSE="$2"

    if [ "$BUILD_TYPE" == "" ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): BUILD_TYPE required"
        return 1
    fi

    # Iterate through list of directories to copy
    for d in $BUILD_AVOIDANCE_SRPM_DIRECTORIES $BUILD_AVOIDANCE_RPM_DIRECTORIES; do
        build_avoidance_copy_dir "$BUILD_TYPE/$d" "$MY_WORKSPACE/$BUILD_TYPE/$d" "$VERBOSE"
        if [ $? -ne 0 ]; then
            >&2 echo "Error: $FUNCNAME (${LINENO}): build_avoidance_copy_dir '$BUILD_TYPE/$d' '$MY_WORKSPACE/$BUILD_TYPE/$d'"
            return 1
        fi
    done

    # Iterate through list of files to copy
    for f in $BUILD_AVOIDANCE_SRPM_FILES $BUILD_AVOIDANCE_RPM_FILES; do
        build_avoidance_copy_file "$BUILD_TYPE/$f" "$MY_WORKSPACE/$BUILD_TYPE/$f" "$VERBOSE"
        if [ $? -ne 0 ]; then
            >&2 echo "Error: $FUNCNAME (${LINENO}): build_avoidance_copy_file '$BUILD_TYPE/$f' '$MY_WORKSPACE/$BUILD_TYPE/$f'"
            return 1
        fi
    done

    return 0
}

#
# build_avoidance_fixups <build-type>
#
# Fix paths in the build artifacts that we coppied that contain
# the user name.
#
# Also, our credentials may differ from the reference build,
# so substitute unsigned packages in place of signed packages.
#
build_avoidance_fixups () {
    local BUILD_TYPE="$1"

    local BA_SOURCE_BUILD_ENVIRONMENT
    BA_SOURCE_BUILD_ENVIRONMENT="${BUILD_AVOIDANCE_USR}-$(basename $(dirname $BUILD_AVOIDANCE_URL))-$(basename $BUILD_AVOIDANCE_URL)-${SRC_BUILD_ENVIRONMENT}"
    local RESULT_DIR=""
    local FROM_DIR=""
    local TO_DIR=""
    local rpm_path_post_signing
    local rpm_path_pre_signing
    local rpm_name
    local md5sum_post_signing
    local md5sum_pre_signing

    if [ "$BUILD_TYPE" == "" ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): BUILD_TYPE required"
        return 1
    fi

    RESULT_DIR="$MY_WORKSPACE/$BUILD_TYPE/results"
    FROM_DIR="${RESULT_DIR}/${BA_SOURCE_BUILD_ENVIRONMENT}-${BUILD_TYPE}"
    TO_DIR="${RESULT_DIR}/${MY_BUILD_ENVIRONMENT}-${BUILD_TYPE}"
    echo "$FUNCNAME: FROM_DIR=$FROM_DIR"
    echo "$FUNCNAME: TO_DIR=$TO_DIR"
    echo "$FUNCNAME: MY_BUILD_ENVIRONMENT=$MY_BUILD_ENVIRONMENT"

    # Fix patchs the use MY_BUILD_ENVIRONMENT
    if [ ! -d "$FROM_DIR" ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): Expected directory '$FROM_DIR' is missing."
        return 1
    fi

    echo "$FUNCNAME: mv '$FROM_DIR' '$TO_DIR'"
    \mv "$FROM_DIR" "$TO_DIR"
    if [ $? -ne 0 ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): mv '$FROM_DIR' '$TO_DIR'"
        return 1
    fi

    local MY_WS_BT="$MY_WORKSPACE/$BUILD_TYPE"

    # Replace signed rpms with non-signed copies .... we aren't a formal build
    for rpm_path_post_signing in $(find $MY_WS_BT/rpmbuild/RPMS -type f -name '*.rpm' | grep -v src.rpm); do

        rpm_name=$(basename $rpm_path_post_signing)
        rpm_path_pre_signing=$(find $MY_WS_BT/results -name $rpm_name | head -n1)
        if [ "$rpm_path_pre_signing" != "" ]; then
            md5sum_post_signing=$(md5sum ${rpm_path_post_signing} | cut -d ' ' -f 1)
            md5sum_pre_signing=$(md5sum ${rpm_path_pre_signing} | cut -d ' ' -f 1)
            if [ "${md5sum_post_signing}" != "${md5sum_pre_signing}" ]; then
                echo "$FUNCNAME: fixing $rpm_name"
                \rm -f ${rpm_path_post_signing}
                if [ $? -ne 0 ]; then
                    >&2 echo "Error: $FUNCNAME (${LINENO}): rm -f ${rpm_path_post_signing}"
                    return 1
                fi

                \cp ${rpm_path_pre_signing} ${rpm_path_post_signing}
                if [ $? -ne 0 ]; then
                    >&2 echo "Error: $FUNCNAME (${LINENO}): cp ${rpm_path_pre_signing} ${rpm_path_post_signing}"
                    return 1
                fi
            fi
        fi;
    done

    return 0
}


#
# build_avoidance <build-type>
#
# Look for a reference build that is applicable to our current git context.
# and copy it to our local workspace, if we haven't already done so.
#
build_avoidance () {
    local BUILD_TYPE="$1"

    echo "==== Build Avoidance Start ===="

    if [ "$BUILD_TYPE" == "" ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): BUILD_TYPE required"
        return 1
    fi

    if [ "$BUILD_TYPE" == "installer" ]; then
        >&2 echo "build_avoidance: BUILD_TYPE==installer not supported"
        return 1
    fi

    if [ "$BUILD_TYPE" == "containers" ]; then
        >&2 echo "build_avoidance: BUILD_TYPE==containers not supported"
        return 1
    fi

    build_avoidance_pre_clean $BUILD_TYPE
    if [ $? -ne 0 ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): build_avoidance_pre_clean $BUILD_TYPE"
        return 1
    fi

    build_avoidance_copy $BUILD_TYPE 'verbose'
    if [ $? -ne 0 ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): build_avoidance_copy $BUILD_TYPE"
        return 1
    fi

    build_avoidance_fixups $BUILD_TYPE
    if [ $? -ne 0 ]; then
        >&2 echo "Error: $FUNCNAME (${LINENO}): build_avoidance_fixups $BUILD_TYPE"
        return 1
    fi

    echo "==== Build Avoidance Complete ===="
    return 0
}

#
# build_avoidance_save_reference_context
#
# For use by a reference build.  Copy the 'CONTEXT' file
# from the build into a central directory where we save
# the context of old builds.
#
# Individual reference builds use:
#     MY_WORKSPACE=<common-dir>/<timestamp>
# and context files are collected in dir:
#     DEST_CTX_DIR=<common-dir>/context
# using name:
#     DEST_CTX=<timestamp>.context

build_avoidance_save_reference_context () {
    local DIR
    DIR=$(dirname "${MY_WORKSPACE}")

    # Note: SUB_DIR should be a timestamp
    local SUB_DIR
    SUB_DIR=$(basename "${MY_WORKSPACE}")

    local SRC_CTX="${MY_WORKSPACE}/CONTEXT"
    local DEST_CTX_DIR="${DIR}/context"
    local DEST_CTX="${DEST_CTX_DIR}/${SUB_DIR}.context"

    if [ ! -f "${SRC_CTX}" ]; then
        echo "Context file not found at '${SRC_CTX}'"
        return 1
    fi

    mkdir -p "${DEST_CTX_DIR}"
    if [ $? -ne 0 ]; then
        echo "Error: $FUNCNAME (${LINENO}): Failed to create directory '${DEST_CTX_DIR}'"
        return 1
    fi

    cp "${SRC_CTX}" "${DEST_CTX}"
    if [ $? -ne 0 ]; then
        echo "Error: $FUNCNAME (${LINENO}): Failed to copy ${SRC_CTX} -> ${DEST_CTX}"
        return 1
    fi

    return 0
}