#!/usr/bin/env bash
#
# USAGE
#   [targets='...'] [mpw_feature=0|1 ...] [CFLAGS='...'] [LDFLAGS='...'] ./build [cc arguments ...]
#
#   By default, you should only need to run ./build
#
#   You can customize the targets that are built using targets='...'. Use targets='all' to build all targets.
#   By default, we only build the 'mpw' target.  
#   See targets_all for all possible targets as well as the features they support and require.
#
#   Several features can be enabled or disabled using feature flags.
#   See the Features section for an overview of the features, their default setting, their meaning and their dependencies.
#   You will need to have each of the feature's dependencies installed for the build to succeed with that feature enabled.
#
#   Finally, the C compiler can be tuned using CFLAGS, LDFLAGS and compiler arguments passed to the script.
#
# BUGS
#   masterpassword@lyndir.com
#
# AUTHOR
#   Maarten Billemont
#
cd "${BASH_SOURCE%/*}"
shopt -s extglob
set -e


### CONFIGURATION
# Targets to build.
targets_all=(
    mpw                     # C CLI version of Master Password (needs: mpw_sodium, optional: mpw_color, mpw_json).
    mpw-bench               # C CLI Master Password benchmark utility (needs: mpw_sodium).
    mpw-tests               # C Master Password algorithm test suite (needs: mpw_sodium, mpw_xml).
)
targets_default='mpw'       # Override with: targets='...' ./build

# Features.
mpw_sodium=${mpw_sodium:-1} # Implement crypto functions with sodium (depends on libsodium).
mpw_json=${mpw_json:-1}     # Support JSON-based user configuration format (depends on libjson-c).
mpw_color=${mpw_color:-1}   # Colorized identicon (depends on libncurses).
mpw_xml=${mpw_xml:-1}       # XML parsing (depends on libxml2).

# Default build flags.
cflags=( -O3 $CFLAGS )
ldflags=( $LDFLAGS )

# Version.
if { mpw_version=$(git describe --match '*-cli*' --long --dirty) || mpw_version=$(<VERSION); } 2>/dev/null; then
    cflags+=( -D"MP_VERSION=$mpw_version" )
fi
echo 2>&1 "Current mpw source version ${mpw_version:-<unknown>}..."


### TARGET: MPW
mpw() {
    # dependencies
    use_mpw_sodium required
    use_mpw_color optional
    use_mpw_json optional

    # target
    cflags=(
        "${cflags[@]}"

        # library paths
        -I"lib/include"
        # mpw paths
        -I"core" -I"cli"
    )
    ldflags=(
        "${ldflags[@]}"
    )

    # build
    cc "${cflags[@]}" "$@" "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" "core/mpw-marshal-util.c" "core/mpw-marshal.c" "cli/mpw-cli-util.c" \
       "${ldflags[@]}"     "cli/mpw-cli.c" -o "mpw"
    echo "done!  You can now run ./mpw-cli-tests, ./install or use ./$_"
}


### TARGET: MPW-BENCH
mpw-bench() {
    # dependencies
    use_mpw_sodium required

    # target
    cflags=(
        "${cflags[@]}"

        # library paths
        -I"lib/include"
        # mpw paths
        -I"core" -I"cli"
    )
    ldflags=(
        "${ldflags[@]}"
    )

    # build
    cc "${cflags[@]}" "$@" "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" \
       "${ldflags[@]}"     "cli/mpw-bench.c" -o "mpw-bench"
    echo "done!  You can now use ./$_"
}


### TARGET: MPW-TESTS
mpw-tests() {
    # dependencies
    use_mpw_sodium required
    use_mpw_xml required

    # target
    cflags=(
        "${cflags[@]}"

        # library paths
        -I"lib/include"
        # mpw paths
        -I"core" -I"cli"
    )
    ldflags=(
        "${ldflags[@]}"
    )

    # build
    cc "${cflags[@]}" "$@" "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" "cli/mpw-tests-util.c" \
       "${ldflags[@]}"     "cli/mpw-tests.c" -o "mpw-tests"
    echo "done!  You can now use ./$_"
}


### TOOLS
haslib() {
    cc -x c "${ldflags[@]}" -l"$1" -o /dev/null - <<< 'int main() { return 0; }' &>/dev/null
}
cc() {
    if hash llvm-gcc 2>/dev/null; then
        llvm-gcc "$@"
    elif hash gcc 2>/dev/null; then
        gcc -std=c11 "$@"
    elif hash clang 2>/dev/null; then
        clang "$@"
    else
        echo >&2 "Need a compiler.  Please install GCC or LLVM."
        exit 1
    fi
}


### DEPENDENCIES
use() {
    local option=$1 requisite=$2 lib=$3; shift 3
    local enabled=${!option}

    if (( enabled )); then
        if haslib "$lib"; then
            for lib in "$lib" "$@"; do
                haslib "$lib" && ldflags+=( -l"$lib" )
            done
            echo >&2 "INFO:     Enabled $option (lib$lib)."
            return 0

        elif [[ $requisite == required ]]; then
            echo >&2 "ERROR:    $option was enabled but is missing $lib library.  Please install this library before continuing."
            exit 1

        else
            echo >&2 "WARNING:  $option was enabled but is missing $lib library.  Will continue with $option disabled!"
            return 1

        fi

    elif [[ $requisite == required ]]; then
        echo >&2 "ERROR:    $option was required but is not enabled.  Please enable the option or remove this target before continuing."
        exit 1

    else
        echo >&2 "INFO:     $option is supported but not enabled."
        return 1
    fi
}
use_mpw_sodium() {
    local requisite=$1
    use mpw_sodium "$requisite" sodium && cflags+=( -D"MPW_SODIUM=1" ) ||:
}
use_mpw_color() {
    local requisite=$1
    use mpw_color "$requisite" curses tinfo && cflags+=( -D"MPW_COLOR=1" ) ||:
}
use_mpw_json() {
    local requisite=$1
    use mpw_json "$requisite" json-c && cflags+=( -D"MPW_JSON=1" ) ||:
}
use_mpw_xml() {
    local requisite=$1
    use mpw_xml "$requisite" xml2 && cflags+=( -D"MPW_XML=1" -I"/usr/include/libxml2" -I"/usr/local/include/libxml2" ) ||:
}


### BUILD TARGETS
for target in "${targets_all[@]}"; do
    if [[ ${targets:-$targets_default} == 'all' || " ${targets:-$targets_default} " = *" $target "*  ]]; then
        echo
        echo "Building target: $target..."
        ( "$target" "$@" )
    fi
done