#!/bin/bash
# Copyright 2023 Google LLC
# SPDX-License-Identifier: BSD-2-Clause
#
# Common code for tests that compares the structure (boxes) of AVIF files
# metadata, dumped as xml, with golden files stored in tests/data/goldens/.
# Depends on the command line tool MP4Box.
#
# To use this, export a function called 'encode_test_files', then
# 'source' this file.
# The paths to avifenc and to the testdata dir will be passed as arguments.
# See 'test_cmd_enc_boxes_golden.sh' for an example.

set -eu

# The CMake config must always be passed. It can be empty if there is
# no multi-config.
CONFIG="$1"
if [[ "$#" -ge 2 ]]; then
  # eval so that the passed in directory can contain variables.
  BINARY_DIR="$(eval echo "$2")"
else
  # Assume "tests" is the current directory.
  BINARY_DIR="$(pwd)/.."
fi
if [[ "$#" -ge 3 ]]; then
  # eval so that the passed in directory can contain variables.
  MP4BOX_DIR="$(eval echo "$3")"
else
  # Assume "tests" is the current directory.
  MP4BOX_DIR="$(pwd)/../ext/gpac/bin/gcc"
fi
if [[ "$#" -ge 4 ]]; then
  TESTDATA_DIR="$(eval echo "$4")"
else
  TESTDATA_DIR="$(pwd)/data"
fi
if [[ "$#" -ge 5 && ! -z "$5" ]]; then
  OUTPUT_DIR="$(eval echo "$5")/test_cmd_enc_boxes_golden"
else
  OUTPUT_DIR="$(mktemp -d)"
fi

GOLDEN_DIR="${TESTDATA_DIR}/goldens"
AVIFENC="${BINARY_DIR}/${CONFIG}/avifenc"
MP4BOX="${MP4BOX_DIR}/MP4Box"

# Colors for pretty formatting.
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NO_COLOR='\033[0m'

if [[ ! -x "$AVIFENC" ]]; then
    echo "'$AVIFENC' does not exist or is not executable"
    exit 1
fi
if [[ ! -x "$MP4BOX" ]]; then
    echo "'$MP4BOX' does not exist or is not executable, build it by running ext/mp4box.sh"
    exit 1
fi

mkdir -p "$OUTPUT_DIR"
echo "Outputting to $OUTPUT_DIR"

has_errors=false

# Cleanup
cleanup() {
    # Only delete temp files if the test succeeded, to allow debugging in case or error.
    if ! $has_errors; then
        find ${OUTPUT_DIR} -type f \( -name '*.avif' -o -name '*.xml' -o -name '*.xml.diff' \) -delete
    fi
}
trap cleanup EXIT

# Replaces parts of the xml that are brittle with 'REDACTED', typically because they depend
# on the size of the encoded pixels.
redact_xml() {
  local f="$1"

  # Remove item data offsets and size so that the xml is stable even if the encoded size changes.
  # The first 2 regexes are for the iloc box, the next 2 are for the mdat box.
  # Note that "-i.bak" works on both Linux and Mac https://stackoverflow.com/a/22084103
  sed -i.bak -e 's/extent_offset="[0-9]*"/extent_offset="REDACTED"/g' \
             -e 's/extent_length="[0-9]*"/extent_length="REDACTED"/g' \
             -e 's/dataSize="[0-9]*"/dataSize="REDACTED"/g' \
             -e 's/<MediaDataBox\(.*\) Size="[0-9]*"/<MediaDataBox\1 Size="REDACTED"/g' \
             "$f"
  # For animations.
  sed -i.bak -e 's/CreationTime="[0-9]*"/CreationTime="REDACTED"/g' \
             -e 's/ModificationTime="[0-9]*"/ModificationTime="REDACTED"/g' \
             -e 's/<Sample\(.*\) size="[0-9]*"/<Sample\1 size="REDACTED"/g' \
             -e 's/<SampleSizeEntry\(.*\) Size="[0-9]*"/<SampleSizeEntry\1 Size="REDACTED"/g' \
             -e 's/<OBU\(.*\) size="[0-9]*"/<OBU\1 size="REDACTED"/g' \
             -e 's/<Tile\(.*\) start="[0-9]*"/<Tile\1 start="REDACTED"/g' \
             -e 's/<Tile\(.*\) size="[0-9]*"/<Tile\1 size="REDACTED"/g' \
            "$f"
  rm "$f.bak"
}

diff_with_goldens() {
    echo "==="

    diff_command=""
    diff_command_with_full_paths=""
    num_files=0
    num_passed=0
    files_with_diffs=""
    files_missing_golden=""

    for f in $(find . -name '*.avif'); do
        num_files=$((num_files + 1))
        echo "Testing $f"
        xml="$(basename "$f").xml" # Remove leading ./ for prettier paths.
        # Dump the file structure as XML
        "${MP4BOX}" -dxml -out "$xml"  "$f"
        redact_xml "$xml"

        # Compare with golden.
        golden="$GOLDEN_DIR/$xml"
        if [[ -f "$golden" ]]; then
            golden_differs=false
            diff -u "$golden" "$xml" > "$xml.diff" || golden_differs=true
            if $golden_differs; then
                has_errors=true
                files_with_diffs="${files_with_diffs}$(basename "$f") "
                echo "FAILED: Differences found for file $xml, see details in $OUTPUT_DIR/$xml.diff" >&2
                echo "Expected file: $golden" >&2
                echo "Actual file: $OUTPUT_DIR/$xml" >&2
                cp "$golden" "$xml.golden" # Copy golden to output directory for easier debugging.

                # "diff --color" is the default value if DIFFTOOL is not set.
                printf -v diff_command "${diff_command}\${DIFFTOOL:-diff --color} $xml.golden $xml\n"
                # Same as diff_command but with absolute paths and a two-space indent.
                printf -v diff_command_with_full_paths "${diff_command_with_full_paths}  \${DIFFTOOL:-diff --color} $OUTPUT_DIR/$xml.golden $OUTPUT_DIR/$xml\n"
            else
                num_passed=$((num_passed + 1))
                # Remove files related to tests that passed to reduce visual noise in the output dir.
                rm "$f"
                rm "$xml"
                rm "$xml.diff"  # Delete empty diff files.
                echo "Passed"
            fi
        else
            files_missing_golden="${files_missing_golden}$(basename "$f") "
            echo "FAILED:  Missing golden file $golden" >&2
            echo "Actual file: $OUTPUT_DIR/$xml" >&2
            has_errors=true
        fi

        echo "---"

    done
    echo "==="
}

pushd "${OUTPUT_DIR}"
    set -x # Print the encoding command lines.
    # Expect this function to be exported by the calling script.
    encode_test_files "${AVIFENC}" "${TESTDATA_DIR}"
    set +x

    diff_with_goldens
popd > /dev/null

if $has_errors; then
  cat >&2 << EOF
$(echo -e "${RED}")Golden test failed. Num passed: ${num_passed}/${num_files}$(echo -e "${NO_COLOR}")
Files that caused diffs: ${files_with_diffs}
Files with missing golden: ${files_missing_golden}

# IF RUNNING ON GITHUB
  Check the workflow step below called "How to fix failing tests" for instructions on how to debug/fix this test.

# IF RUNNING LOCALLY
  View diffs with: (set DIFFTOOL to your favorite tool if needed)
$(echo -e "${BLUE}")$diff_command_with_full_paths$(echo -e "${NO_COLOR}")
  Update golden files with:
  $(echo -e "${BLUE}")cp $OUTPUT_DIR/*.xml "$GOLDEN_DIR"$(echo -e "${NO_COLOR}")
EOF

    cat > "$OUTPUT_DIR/README.txt" << EOF
To debug test failures, look at the .xml.diff files or run (from this directory):

$diff_command

If the diffs are expected, update the golden files by copying the .xml files, e.g.:
cp *.xml $HOME/git/libavif/tests/data/goldens

You can also debug the test locally by setting AVIF_ENABLE_GOLDEN_TESTS=ON in cmake.
EOF

    exit 1
else
  echo -e "${GREEN}Golden tests passed (${num_passed} tests)${NO_COLOR}"
fi

exit 0
