#!/bin/bash

trap "echo Exited!; exit 1;" SIGINT SIGTERM

PROJECT_DIR="$(cd "$(dirname "$0")/.."; pwd)"

function print_usage {
    echo "usage: test [subcommand] [go test args]"
    echo
    echo -e "\033[1mSubcommands:\033[0m"
    echo "   all               Run all the tests, excluding linters (default)"
    echo "   cleaners          Run tools that clean the code base"
    echo "   unit              Run the unit tests"
    echo "   linters           Run common linters against the project"
    echo "   ops_files         Run tests for operation files"
    echo "   install_tools     Install all necessary binaries for these scripts"
}

function print_checkpoint {
    echo
    bold_blue "==================================  $@"
}

function green {
    echo -e "\e[32m$1\e[0m"
}

function red {
    echo -e "\e[31m$1\e[0m"
}

function bold_blue {
    echo -e "\e[1;34m$1\e[0m"
}

function check_output {
    eval "$@"
    local status=$?
    exit_on_failure $status
}

function exit_on_failure {
    if [[ $1 -ne 0 ]]; then
        red "SUITE FAILURE"
        exit $1
    fi
}

function run_cleaners {
    print_checkpoint "Running Cleaners"

    go get github.com/kisielk/gotool

    if ! which goimports > /dev/null 2>&1; then
        echo installing goimports
        go get golang.org/x/tools/cmd/goimports
    fi
    if ! which misspell > /dev/null 2>&1; then
        echo installing misspell
        go get github.com/client9/misspell/cmd/misspell
    fi
    if ! which unconvert > /dev/null 2>&1; then
        echo installing unconvert
        go get github.com/mdempsky/unconvert
    fi

    local log_cache_pkg="code.cloudfoundry.org/log-cache"
    local log_cache_dir="$(dirname $(dirname $0))/src/$log_cache_pkg"
    echo running goimports
    goimports -w "$log_cache_dir"
    echo running gofmt
    gofmt -s -w "$log_cache_dir"
    echo running misspell
    misspell -w "$log_cache_dir"
    echo running unconvert
    unconvert -v -apply "$log_cache_pkg/..."
    echo running go vet
    go vet "$log_cache_pkg/..."
    return 0
}

function run_unit {
    pushd $PROJECT_DIR/src > /dev/null
        TEST_OPTIONS="-ginkgo.randomizeAllSpecs -ginkgo.slowSpecThreshold 20"

        # The race detector dramatically increases the runtime of certain tests
        # As a result, we first run the tests with race detection
        # enabled, but only consider actual data races as failures. We then run the test suite
        # without race detection and assert on test correctness.
        print_checkpoint "Checking for Data Races"
        race_log=$(mktemp log_cache_race_log.XXXXXXXX --tmpdir)

        go test -mod=vendor -race ./... \
            ${TEST_OPTIONS} \
            $@ 2>&1 > ${race_log} || true
        if egrep 'DATA RACE|race detected' ${race_log}; then
            cat ${race_log}
            rm ${race_log}
            return 1
        fi
        rm ${race_log}
        echo "No races found"

        print_checkpoint "Running Unit Tests"
        go test ./... \
            ${TEST_OPTIONS} \
            $@
        exit_code=$?
    popd > /dev/null
    return $exit_code
}

function run_all {
    check_output run_cleaners
    check_output run_unit $@
}

function parse_argc {
    command=run_all
    if [[ $# -eq 0 ]]; then
        return
    fi

    arg=$1
    case "$arg" in
        -h|-help|--help|help)
            print_usage
            exit 0
            ;;
        all|unit|cleaners|linters|install_tools|ops_files)
            command=run_$arg
            ;;
        *)
            echo "Invalid command: $arg\n"
            print_usage
            exit 1
            ;;
    esac
}

function run_install_tools {
    print_checkpoint "Installing Tools"

    # testing
    go get github.com/onsi/ginkgo/ginkgo

    # cleaners
    go get golang.org/x/tools/cmd/goimports
    go get github.com/client9/misspell/cmd/misspell
    go get github.com/mdempsky/unconvert

    # linters
    go get github.com/tsenart/deadcode
    go get github.com/golang/lint/golint
    go get github.com/opennota/check/cmd/aligncheck
    go get github.com/opennota/check/cmd/structcheck
    go get github.com/opennota/check/cmd/varcheck
    go get github.com/kisielk/errcheck
    go get github.com/gordonklaus/ineffassign
    go get mvdan.cc/interfacer
    go get honnef.co/go/tools/cmd/megacheck
}

function run_linters {
    print_checkpoint "Running Linters"

    local log_cache_pkg
    if [ "$1" = "" ]; then
        log_cache_pkg="code.cloudfoundry.org/log-cache"
    else
        log_cache_pkg="$1"
    fi
    local log_cache_dir="$(dirname $(dirname $0))/src/$log_cache_pkg"

    echo running go vet
    go vet "$log_cache_pkg/..."
    echo running deadcode
    deadcode $dir
    echo running golint
    golint "$log_cache_pkg/..."
    echo running aligncheck
    aligncheck "$log_cache_pkg/..."
    echo running structcheck
    structcheck "$log_cache_pkg/..."
    echo running varcheck
    varcheck "$log_cache_pkg/..."
    echo running errcheck
    errcheck -ignore '[cC]lose' "$log_cache_pkg/..."
    echo running ineffassign
    ineffassign "$log_cache_dir"
    echo running interfacer
    interfacer "$log_cache_pkg/..."
    echo running megacheck
    megacheck "$log_cache_pkg/..."

    return 0
}

function run_ops_files {
    print_checkpoint "Running Operation File Tests"

    pushd $PROJECT_DIR > /dev/null
        echo creating deploy-in-cf.yml ops file
        tmp_dir=`mktemp -d`

        cat <<EOF > $tmp_dir/consumes_cf.yml
- type: replace
  path: /instance_groups/name=log-cache/jobs/name=log-cache/consumes/reverse_log_proxy/deployment?
  value: cf
EOF

        bosh int manifests/log-cache.yml --ops-file=$tmp_dir/consumes_cf.yml > $tmp_dir/adjusted.yml

        ops_file_path=manifests/operations/deploy-in-cf.yml
        instance_groups=`bosh int $tmp_dir/adjusted.yml --path /instance_groups | sed 's/^\-/ /' | sed 's/^/  /'`
        variables=`bosh int $tmp_dir/adjusted.yml --path /variables | sed 's/^\-/ /' | sed 's/^/  /'`

        cat <<EOF > manifests/operations/deploy-in-cf.yml
- type: replace
  path: /releases/-
  value:
    name: log-cache
    version: latest
- type: replace
  path: /instance_groups/-
  value:
$instance_groups
- type: replace
  path: /variables/-
  value:
$variables
EOF
    popd > /dev/null

    return 0
}

function setup_env {
    export PATH="$PROJECT_DIR/bin:$PATH"
    export GORACE="halt_on_error=1"
}

function main {
    setup_env
    parse_argc $1
    shift
    "$command" $@
    result=$?
    exit_on_failure $result
    green "SWEET SUITE SUCCESS"
}

main $@
