#!/usr/bin/env bash
# Depends on bash 4 and gawk

TOPDIR=$(git rev-parse --show-toplevel)
# Work in a temp dir so that developers can continue working while a build is
# in progress
WORKDIR=$(mktemp -d /tmp/kolla-workdir.XXXXXXXXXX)
# Remove $WORKDIR otherwise $TOPDIR is copied *inside* of it
rm -rf $WORKDIR
cp -aL "$TOPDIR" $WORKDIR
DOCKERDIR="$WORKDIR/docker"

declare -A dependency
declare -A img_dirs
declare -A status

function info {
    [[ -n $1 ]] && printf "%s\n" "$1"
}

function success {
    [[ -n $1 ]] && printf "\033[00;32m%s\033[00m\n" "$1"
}

function warn {
    [[ -n $1 ]] && printf "\033[00;31m%s\033[00m\n" "$1"
}

function set_defaults {
    PREFIX=centos-rdo-
    NAMESPACE=kollaglue
    [ -f $WORKDIR/.buildconf ] && . $WORKDIR/.buildconf
}

function has_changed {
    local image=$1
    # Rebuild everything unless given git revision
    # We don't really care about the order of the $FROM and $TO parameters, all
    # we need is the list of changed files between the revisions, or HEAD if
    # only one of them is specified
    [[ -z $FROM && -z $TO ]] || git diff --name-only $FROM $TO | grep -q "${img_dirs[$image]#$WORKDIR/}"
}

function requires_build {
    local image=$1
    local dep=${dependency[$image]}
    # An image requires a built if it meets the following conditions:
    # - it has instructions to build the image
    # - it hasn't been processed yet
    # - its dependency was rebuilt or its build instruction was modified
    [[ ${img_dirs[$image]} && -z ${status[$image]} ]] && \
        ([[ ${status[$dep]} == "rebuilt" ]] || has_changed $image)
}

function build_image {
    local dir=$1
    printf "\n"
    if [ -x "$dir/build" ]; then
        info "Building image in $dir"
        if $dir/build $ARGS --no-use-released-parent; then
            success "Successfully built image in $dir"
            status[$image]="rebuilt"
        else
            warn "Failed to build image in $dir"
            status[$image]="fail"
        fi
    else
        warn "Image $image does not provide build script"
        status[$image]="fail"
    fi
}

function init_image {
    local img_dir=$1

    set_defaults
    [ -f $WORKDIR/.buildconf ] && . $WORKDIR/.buildconf
    [ -f $img_dir/.buildconf ] && . $img_dir/.buildconf
    [ -n "$FORCE_NAMESPACE" ]  && NAMESPACE=$FORCE_NAMESPACE
    [ -n "${REGISTRY}" ]  && NAMESPACE="${REGISTRY}/${NAMESPACE}"

    local image="${NAMESPACE:+${NAMESPACE}/}${PREFIX}${img_dir##*/}"
    local base_image=$(cat $img_dir/Dockerfile | gawk 'match($0, /^\s*FROM\s+(\S+)/, matches) {print matches[1]}' )
    base_image=${base_image//%%KOLLA_NAMESPACE%%/$NAMESPACE}
    base_image=${base_image//%%KOLLA_PREFIX%%/$PREFIX}
    base_image=${base_image//:%%KOLLA_TAG%%/}

    img_dirs[$image]=$img_dir
    dependency[$image]=$base_image

    # Restore defaults to minimize risk of side effects
    set_defaults
}

function process_image {
    local image=$1
    if [ -n "${dependency[$image]}" ]; then
        process_image ${dependency[$image]}
    fi
    if requires_build $image; then
        build_image ${img_dirs[$image]}
    fi
    if [ -z "${status[$image]}" ]; then
        status[$image]="up-to-date"
    fi
}

function print_summary {
    printf "\nSummary\n=======\n"
    for image in "${!status[@]}"; do
        case "${status[$image]}" in
            ("fail")    warn    "Failed to process $image" ;;
            ("rebuilt") success "Rebuilt $image" ;;
            (*)         info    "$image: ${status[$image]}" ;;
        esac
    done
}

function print_parseable_summary {
    printf "{"
    for image in "${!status[@]}"; do
        printf "'$image':'${status[$image]}',"
    done
    printf "}"
}

function interrupted {
    info "Interrupted..."
    print_summary
    rm -rf $WORKDIR
    exit 1
}

function usage {
    read -r -d '' HELP <<EOF
A wrapper to build-docker-image that build all images in order.

Options:

--from <git-revision>
--to <git-revision>

$($WORKDIR/tools/build-docker-image --help)
EOF
    printf "%s\n" "${HELP/$WORKDIR\/tools\/build-docker-image/$0}"
}

trap 'interrupted' INT


ARGS=$@
PARSED_ARGS=$(getopt -q -o hr:n:t: -l help,namespace:,release,tag:,private-registry:,from:,to:,testmode -- "$@")

eval set -- "$PARSED_ARGS"

while :; do
    case "$1" in

    (--help|-h) usage
                exit 0
                ;;

    (--namespace|-n)
                shift
                FORCE_NAMESPACE="$1"
                ;;

    (--release)
                RELEASE_SPECIFIED=1
                ;;

    (--tag|-t)
                shift
                TAG_SPECIFIED=1
                ;;

    (--private-registry|-r)
                shift
                REGISTRY="$1"
                ;;

    (--from)
                shift
                FROM="$1"
                ARGS=${ARGS/\-\-from*$FROM/}
                ;;

    (--to)
                shift
                TO="$1"
                ARGS=${ARGS/\-\-to*$TO/}
                ;;

    (--testmode)
                TESTMODE=1
                ARGS=${ARGS/\-\-testmode/}
                ;;

    (--)        break
                ;;

    esac

    shift
done

set_defaults

# BASE == centos, fedora, ubuntu, etc.
BASE=$(echo "${PREFIX}" | cut -d- -f1)
# TYPE == binary, source, rdo
TYPE=$(echo "${PREFIX}" | cut -d- -f2)

# Ensure a tag is specified otherwise build may fail when changing git branch
# The release tag is automatically appended when --release flag is used.
if [[ $RELEASE_SPECIFIED != 1 ]] && [[ $TAG_SPECIFIED != 1 ]]; then
    TAG=$(git rev-parse --short HEAD)
    ARGS+=" --tag $TAG"
fi

# Do a first pass to find images to build and their dependencies
for dockerfile in $(find "${DOCKERDIR}/${BASE}/${TYPE}" -name Dockerfile); do
    init_image $(dirname $dockerfile)
done

# Process all images
for image in "${!img_dirs[@]}"; do
    process_image $image
done

print_summary
[ -n "$TESTMODE" ] && print_parseable_summary
rm -rf $WORKDIR
