#! /usr/bin/env bash
# Copyright (C) Sebastian Pipping <sebastian@pipping.org>
# Licensed under the MIT license

export PS4='# '


_get_source_dir() {
    echo "source__${version}"
}


_get_build_dir() {
    local libbsd_part=
    if ${with_libbsd}; then
        libbsd_part=__libbsd
    fi

    local mingw_part=
    if ${with_mingw}; then
        mingw_part=__windows
    fi

    local char_part=
    if ${with_unsigned_char}; then
        char_part=__unsigned_char
    fi

    local xml_attr_part=
    if ${xml_attr_info_enabled}; then
        xml_attr_part=__attr_info
    fi

    echo "build__${version}__unicode_${unicode_enabled}__xml_context_${xml_context}${libbsd_part}${mingw_part}${char_part}${xml_attr_part}"
}


_get_coverage_dir() {
    echo "coverage__${version}"
}


_call_cmake() {
    local cmake_args=()

    ${unicode_enabled} \
            && cmake_args+=( -DEXPAT_CHAR_TYPE=wchar_t )

    ${xml_attr_info_enabled} \
            && cmake_args+=( -DEXPAT_ATTR_INFO=ON )

    if [[ ${xml_context} -eq 0 ]]; then
        cmake_args+=( -DEXPAT_CONTEXT_BYTES=OFF )
    else
        cmake_args+=( -DEXPAT_CONTEXT_BYTES=${xml_context} )
    fi

    ${with_libbsd} && cmake_args+=( -DEXPAT_WITH_LIBBSD=ON )
    ${with_mingw} && cmake_args+=( -DCMAKE_TOOLCHAIN_FILE="${abs_source_dir}"/cmake/mingw-toolchain.cmake )

    (
        set -x
        cmake "${cmake_args[@]}" "$@" . &>> cmake.log
    )
}


_copy_to() {
    local target_dir="$1"
    [[ -d "${target_dir}" ]] && return 0

    mkdir "${target_dir}"
    git archive --format=tar "${version}" | ( cd "${target_dir}" && tar x )
}


_copy_missing_mingw_libaries() {
    # These extra files are copied because
    # * coverage GCC flags make them needed
    # * With WINEDLLPATH Wine looks for .dll.so in these folders, not .dll
    local target="$1"
    local mingw_gcc_dll_dir="$(dirname "$(ls -1 /usr/lib*/gcc/i686-w64-mingw32/*/libgcc_s_sjlj-1.dll | head -n1)")"
    for dll in libgcc_s_sjlj-1.dll libstdc++-6.dll; do
        (
            set -x
            ln -s "${mingw_gcc_dll_dir}"/${dll} "${target}"/${dll}
        )
    done

    local mingw_pthread_dll="$(ls -1 /usr/i686-w64-mingw32/lib*/libwinpthread-1.dll 2>/dev/null | head -n1)"
    if [[ -n ${mingw_pthread_dll} ]]; then
        local mingw_pthread_dll_dir="$(dirname "${mingw_pthread_dll}")"
        for dll in libwinpthread-1.dll; do
            source="${mingw_pthread_dll_dir}"/${dll}
            (
                set -x
                ln -s "${source}" "${target}"/${dll}
            )
        done
    fi

    for dll in libexpat{,w}.dll; do
        (
            set -x
            ln -s "${abs_build_dir}"/${dll} "${target}"/${dll}
        )
    done
}


_run() {
    local source_dir="$1"
    local build_dir="$2"
    local abs_source_dir="${PWD}/${source_dir}"
    local abs_build_dir="${PWD}/${build_dir}"
    local capture_dir=.

    local BASE_FLAGS='-pipe -Wall -Wextra -pedantic -Wno-overlength-strings'
    BASE_FLAGS+=' --coverage --no-inline'

    ${with_unsigned_char} && BASE_FLAGS="${BASE_FLAGS} -funsigned-char"

    local CFLAGS="-std=c99 ${BASE_FLAGS}"
    local CXXFLAGS="-std=c++98 ${BASE_FLAGS}"

    (
        set -e
        cd "${build_dir}"

        _call_cmake \
                -DCMAKE_C_FLAGS="${CFLAGS}" \
                -DCMAKE_CXX_FLAGS="${CXXFLAGS}"

        (
            set -x
            make &> build.log

            lcov -c -d "${capture_dir}" -i -o "${coverage_info}-zero" &> run.log
        )

        if ${with_mingw}; then
            for d in tests xmlwf ; do
                mkdir -p "${d}"
                _copy_missing_mingw_libaries "${d}"
            done
        fi

        set -x
        make CTEST_OUTPUT_ON_FAILURE=1 test run-xmltest

        lcov -c -d "${capture_dir}" -o "${coverage_info}-test" &>> run.log
        lcov \
                -a "${coverage_info}-zero" \
                -a "${coverage_info}-test" \
                -o "${coverage_info}-all" \
                &>> run.log

        # Make sure that files overlap in report despite different build folders
        sed "/SF:/ s,${build_dir}/,${source_dir}/," "${coverage_info}-all" > "${coverage_info}"
    ) |& sed 's,^,  ,'
    res=${PIPESTATUS[0]}

    if [[ ${res} -eq 0 ]]; then
        echo PASSED
    else
        echo FAILED >&2
        return 1
    fi
}


_merge_coverage_info() {
    local coverage_dir="$1"
    shift
    local build_dirs=( "$@" )

    mkdir -p "${coverage_dir}"
    (
        local lcov_merge_args=( -q )
        for build_dir in "${build_dirs[@]}"; do
            lcov_merge_args+=( -a "${build_dir}/${coverage_info}" )
        done
        lcov_merge_args+=( -o "${coverage_dir}/${coverage_info}" )

        set -x
        lcov "${lcov_merge_args[@]}"
    ) |& tee "${coverage_dir}/merge.log"
}


_clean_coverage_info() {
    local coverage_dir="$1"
    local dir
    for dir in CMakeFiles examples tests ; do
        local pattern="*/${dir}/*"
        (
            set -x
            lcov -q -o "${coverage_dir}/${coverage_info}" -r "${coverage_dir}/${coverage_info}" "${pattern}"
        ) |& tee "${coverage_dir}/clean.log"
    done
}


_render_html_report() {
    local coverage_dir="$1"
    (
        set -x
        genhtml -o "${coverage_dir}" "${coverage_dir}/${coverage_info}" &> "${coverage_dir}/render.log"
    )
}


_show_summary() {
    local coverage_dir="$1"
    (
        set -x
        lcov -q -l "${coverage_dir}/${coverage_info}"
    ) | grep -v '^\['
}


_main() {
    version="$(git describe --tags)"
    coverage_info=coverage.info

    local build_dirs=()
    local source_dir="$(_get_source_dir)"
    local coverage_dir="$(_get_coverage_dir)"

    _copy_to "${source_dir}"

    _build_case() {
        local build_dir="$(_get_build_dir)"

        echo "[${build_dir}]"
        _copy_to "${build_dir}"

        # Make sure we don't need to download xmlts.zip over and over again
        if [[ ${#build_dirs[*]} -gt 0 ]]; then
            ln -s "$PWD/${build_dirs[0]}/tests/xmlts.zip" "${build_dir}"/tests/
        fi

        _run "${source_dir}" "${build_dir}"

        build_dirs+=( "${build_dir}" )
    }

    # All combinations:
    with_unsigned_char=false
    with_libbsd=false
    for with_mingw in true false ; do
        for unicode_enabled in true false ; do
            if ${unicode_enabled} && ! ${with_mingw} ; then
                continue
            fi

            for xml_attr_info_enabled in true false ; do
                for xml_context in 0 1024 ; do
                    _build_case
                done
            done
        done
    done

    # Single cases:
    with_libbsd=true _build_case
    with_unsigned_char=true _build_case

    echo
    echo 'Merging coverage files...'
    _merge_coverage_info "${coverage_dir}" "${build_dirs[@]}"

    echo
    echo 'Cleaning coverage files...'
    _clean_coverage_info "${coverage_dir}"

    echo
    echo 'Rendering HTML report...'
    _render_html_report "${coverage_dir}"
    echo "--> ${coverage_dir}/index.html"

    echo
    echo 'Rendering ASCII report...'
    _show_summary "${coverage_dir}"
}


_main
