#!/usr/bin/env bash

set -euo pipefail

#
# variables
#

RESET="\033[0m"
RED="\033[0;31m"
YELLOW="\033[0;33m"
MAGENTA="\033[0;95m"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
verbose=false
update=false
reinstall=false
lockfile_path="$DIR/korebuild-lock.txt"
config_file="$DIR/korebuild.json"
channel='master'
tools_source='https://aspnetcore.blob.core.windows.net/buildtools'
ci=false
run_restore=true
run_build=true
run_pack=false
run_tests=false
build_all=false
build_managed=false
build_nodejs=false
build_projects=''
msbuild_args=()

#
# Functions
#
__usage() {
    echo "Usage: $(basename "${BASH_SOURCE[0]}") [options] [[--] <Arguments>...]

Arguments:
    <Arguments>...     Arguments passed to the command. Variable number of arguments allowed.

Options:
    --[no-]restore     Run restore.
    --[no-]build       Compile projects
    --[no-]pack        Produce packages.
    --[no-]test        Run tests.

    --projects         A list of projects to build. (Must be an absolute path.)
                       Globbing patterns are supported, such as \"$(pwd)/**/*.csproj\".

    --all              Build all project types.
    --managed          Build managed projects (C#, F#, VB).
    --nodejs           Build NodeJS projects (TypeScript, JS).

    --ci               Apply CI specific settings and environment variables.
    --verbose          Show verbose output.

Description:
    This build script installs required tools and runs an MSBuild command on this repository
    This script can be used to invoke various targets, such as targets to produce packages
    build projects, run tests, and generate code.
"

    if [[ "${1:-}" != '--no-exit' ]]; then
        exit 2
    fi
}

get_korebuild() {
    local version
    if [ ! -f "$lockfile_path" ]; then
        __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lockfile_path"
    fi
    version="$(grep 'version:*' -m 1 "$lockfile_path")"
    if [[ "$version" == '' ]]; then
        __error "Failed to parse version from $lockfile_path. Expected a line that begins with 'version:'"
        return 1
    fi
    version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
    local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version"

    {
        if [ ! -d "$korebuild_path" ]; then
            mkdir -p "$korebuild_path"
            local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip"
            tmpfile="$(mktemp)"
            echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}"
            if __get_remote_file "$remote_path" "$tmpfile"; then
                unzip -q -d "$korebuild_path" "$tmpfile"
            fi
            rm "$tmpfile" || true
        fi

        source "$korebuild_path/KoreBuild.sh"
    } || {
        if [ -d "$korebuild_path" ]; then
            echo "Cleaning up after failed installation"
            rm -rf "$korebuild_path" || true
        fi
        return 1
    }
}

__error() {
    echo -e "${RED}error: $*${RESET}" 1>&2
}

__warn() {
    echo -e "${YELLOW}warning: $*${RESET}"
}

__machine_has() {
    hash "$1" > /dev/null 2>&1
    return $?
}

__get_remote_file() {
    local remote_path=$1
    local local_path=$2

    if [[ "$remote_path" != 'http'* ]]; then
        cp "$remote_path" "$local_path"
        return 0
    fi

    local failed=false
    if __machine_has wget; then
        wget --tries 10 --quiet -O "$local_path" "$remote_path" || failed=true
    else
        failed=true
    fi

    if [ "$failed" = true ] && __machine_has curl; then
        failed=false
        curl --retry 10 -sSL -f --create-dirs -o "$local_path" "$remote_path" || failed=true
    fi

    if [ "$failed" = true ]; then
        __error "Download failed: $remote_path" 1>&2
        return 1
    fi
}

#
# main
#

while [[ $# -gt 0 ]]; do
    case $1 in
        -\?|-h|--help)
            __usage --no-exit
            exit 0
            ;;
        --restore|-[Rr]estore)
            run_restore=true
            ;;
        --no-restore)
            run_restore=false
            ;;
        --build|-[Bb]build)
            run_build=true
            ;;
        --no-build)
            run_build=false
            ;;
        --pack|-[Pp]ack)
            run_pack=true
            ;;
        --no-pack)
            run_pack=false
            ;;
        --test|-[Tt]est)
            run_tests=true
            ;;
        --no-test)
            run_tests=false
            ;;
        --projects|-[Pp]rojects)
            shift
            build_projects="${1:-}"
            [ -z "$build_projects" ] && __error "Missing value for parameter --projects" && __usage
            ;;
        --all|-[Aa]ll)
            build_all=true
            ;;
        --managed|-[Mm]anaged)
            build_managed=true
            ;;
        --nodejs|-[Nn]ode[Jj][Ss])
            build_nodejs=true
            ;;
        --native|-[Nn]ative)
            __warn 'The C++ projects in this repo only build on Windows. The --native flag will be ignored.'
            ;;
        --ci|-[Cc][Ii])
            ci=true
            if [[ -z "${DOTNET_HOME:-}" ]]; then
                DOTNET_HOME="$DIR/.dotnet"
            fi
            ;;
        --verbose|-[Vv]erbose)
            verbose=true
            ;;
        *)
            msbuild_args[${#msbuild_args[*]}]="$1"
            ;;
    esac
    shift
done

if ! __machine_has unzip; then
    __error 'Missing required command: unzip'
    exit 1
fi

if ! __machine_has curl && ! __machine_has wget; then
    __error 'Missing required command. Either wget or curl is required.'
    exit 1
fi

if [ -f "$config_file" ]; then
    if __machine_has jq ; then
        if jq '.' "$config_file" >/dev/null ; then
            config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")"
            config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")"
        else
            __error "$config_file is invalid JSON. Its settings will be ignored."
            exit 1
        fi
    elif __machine_has python ; then
        if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
            config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
            config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
        else
            __error "$config_file is invalid JSON. Its settings will be ignored."
            exit 1
        fi
    else
        __error 'Missing required command: jq or python. Could not parse the JSON file. Its settings will be ignored.'
        exit 1
    fi

    [ ! -z "${config_channel:-}" ] && channel="$config_channel"
    [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source"
fi

[ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet"
export DOTNET_HOME="$DOTNET_HOME"

get_korebuild

if [ "$build_all" = true ]; then
    msbuild_args[${#msbuild_args[*]}]="-p:BuildAllProjects=true"
elif [ ! -z "$build_projects" ]; then
    msbuild_args[${#msbuild_args[*]}]="-p:Projects=$build_projects"
else
    # When adding new sub-group build flags, add them to this check
    if [ "$build_managed" = false ] && [ "$build_nodejs" = false ]; then
        # This goal of this is to pick a sensible default for `build.sh` with zero arguments.
        # We believe the most common thing our contributors will work on is C#, so if no other build group was picked, build the C# projects.
        __warn "No default group of projects was specified, so building the 'managed' subset of projects. Run ``build.sh -help`` for more details."
        build_managed=true
    fi

    msbuild_args[${#msbuild_args[*]}]="-p:BuildManaged=$build_managed"
    msbuild_args[${#msbuild_args[*]}]="-p:BuildNodeJS=$build_nodejs"
fi

msbuild_args[${#msbuild_args[*]}]="-p:_RunRestore=$run_restore"
msbuild_args[${#msbuild_args[*]}]="-p:_RunBuild=$run_build"
msbuild_args[${#msbuild_args[*]}]="-p:_RunPack=$run_pack"
msbuild_args[${#msbuild_args[*]}]="-p:_RunTests=$run_tests"

# Disable downloading ref assemblies as a tarball. Use netfx refs from the Microsoft.NETFramework.ReferenceAssemblies NuGet package instead.
[ -z "${KOREBUILD_SKIP_INSTALL_NETFX:-}" ] && KOREBUILD_SKIP_INSTALL_NETFX=1

set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$DIR" "$config_file" "$ci"

# This incantation avoids unbound variable issues if msbuild_args is empty
# https://stackoverflow.com/questions/7577052/bash-empty-array-expansion-with-set-u
invoke_korebuild_command 'default-build' ${msbuild_args[@]+"${msbuild_args[@]}"}
