#!/usr/bin/env bash
set -euo pipefail

# Test functionality of cmprss by comparing it with the official tools

CACHE_DIR="${PRJ_ROOT}/.cache"

# Create a tmp directory and cd into it
tmpdir() {
  mkdir -p "$CACHE_DIR"
  local dir
  dir=$(mktemp --directory --tmpdir="$CACHE_DIR")
  cd "$dir"
}

# Compare the two files/directories and exit with an error if they are different
compare() {
  local file1="$1"
  local file2="$2"
  if ! diff -qr "$file1" "$file2" >/dev/null; then
    echo "Diff Detected: $file1 $file2"
    exit 1
  fi
}

# Compare the two archive sizes to check if they are similar
# TODO: This doesn't work anywhere in the file because we're compressing random data
# So the algos will always fail to compress things very well
compare_size() {
  local file1="$1"
  local file2="$2"
  # Use $3 as the max difference or 100 bytes
  local max_diff=${3:-100}
  local size1
  local size2
  size1=$(stat -c %s "$file1")
  size2=$(stat -c %s "$file2")
  if [ $((size1 - size2)) -gt "$max_diff" ]; then
    echo "Size difference too large: $file1:$size1 $file2:$size2"
    exit 1
  fi
}

# Create a random file with the given size
random_file() {
  local size="$1"
  local file="$2"
  dd if=/dev/urandom of="$file" bs=1 count="$size" 2>/dev/null
}

# Create a random directory with the given size
random_dir() {
  local size="$1"
  local dir="$2"
  mkdir -p "$dir"
  for i in $(seq 1 "$size"); do
    random_file 128 "$dir/$i"
  done
}

# Run cmprss using cargo to test the current version
cmprss() {
  cargo run --release --quiet -- "$1" --ignore-pipes "${@:2}"
}

# Test gzip using the provided compression level
test_gzip_level() {
  tmpdir
  echo "Testing gzip level $1 in $PWD"
  echo "Creating random data"
  random_file 1000000 file
  echo "Compressing with gzip and cmprss"
  gzip -"$1" -c file >gzip_file.gz
  cmprss gzip --level "$1" file cmprss_file.gz --progress=off
  # Compare the two archives
  # The archives may have slight variations (versioning or whatever) so we
  # only compare the sizes to make sure they are similar
  compare_size gzip_file.gz cmprss_file.gz
  # Decompress the 4 variations
  echo "Decompressing"
  gzip -c -d gzip_file.gz >gzip_gzip
  gzip -c -d cmprss_file.gz >cmprss_gzip
  cmprss gzip --extract cmprss_file.gz cmprss_cmprss --progress=off
  cmprss gzip --extract gzip_file.gz gzip_cmprss --progress=off
  echo "Comparing the decompressed files"
  compare file gzip_gzip
  compare file gzip_cmprss
  compare file cmprss_cmprss
  compare file cmprss_gzip
  echo "No errors detected"
}

test_gzip() {
  test_gzip_level 1
  test_gzip_level 6 # Default
  test_gzip_level 9
}

# Test xz using the provided compression level
test_xz_level() {
  tmpdir
  echo "Testing xz level $1 in $PWD"
  echo "Creating random data"
  random_file 1000000 file
  echo "Compressing with xz and cmprss"
  xz -"$1" --stdout file >xz_file.xz
  cmprss xz --level "$1" file cmprss_file.xz --progress=off
  # Compare the two archives
  # The archives may have slight variations (versioning or whatever) so we
  # only compare the sizes to make sure they are similar
  compare_size xz_file.xz cmprss_file.xz
  # Decompress the 4 variations
  echo "Decompressing"
  xz --stdout --decompress xz_file.xz >xz_xz
  xz --stdout --decompress cmprss_file.xz >xz_cmprss
  cmprss xz --extract cmprss_file.xz cmprss_cmprss --progress=off
  cmprss xz --extract xz_file.xz cmprss_xz --progress=off
  echo "Comparing the decompressed files"
  compare file xz_xz
  compare file xz_cmprss
  compare file cmprss_cmprss
  compare file cmprss_xz
  echo "No errors detected"
}

test_xz() {
  test_xz_level 0 # No compression
  test_xz_level 1
  test_xz_level 6 # Default
  test_xz_level 9
}

# Test bzip2 using the provided compression level
test_bzip2_level() {
  tmpdir
  echo "Testing bzip2 level $1 in $PWD"
  echo "Creating random data"
  random_file 1000000 file
  echo "Compressing with bzip2 and cmprss"
  bzip2 -"$1" --stdout file >bzip2_file.bz2
  cmprss bzip2 --level "$1" file cmprss_file.bz2 --progress=off
  # Compare the two archives
  # The archives may have slight variations (versioning or whatever) so we
  # only compare the sizes to make sure they are similar
  compare_size bzip2_file.bz2 cmprss_file.bz2
  # Decompress the 4 variations
  echo "Decompressing"
  bzip2 --stdout --decompress bzip2_file.bz2 >bzip2_bzip2
  bzip2 --stdout --decompress cmprss_file.bz2 >cmprss_bzip2
  cmprss bzip2 --extract cmprss_file.bz2 cmprss_cmprss --progress=off
  cmprss bzip2 --extract bzip2_file.bz2 bzip2_cmprss --progress=off
  echo "Comparing the decompressed files"
  compare file bzip2_bzip2
  compare file bzip2_cmprss
  compare file cmprss_cmprss
  compare file cmprss_bzip2
  echo "No errors detected"
}

test_bzip2() {
  test_bzip2_level 1
  test_bzip2_level 6
  test_bzip2_level 9 # Default
}

# Test zstd using the provided compression level
test_zstd_level() {
  tmpdir
  echo "Testing zstd level $1 in $PWD"
  echo "Creating random data"
  random_file 1000000 file
  echo "Compressing with zstd and cmprss"
  zstd -"$1" -c file >zstd_file.zst
  cmprss zstd --level "$1" file cmprss_file.zst --progress=off
  # Compare the two archives
  # The archives may have slight variations (versioning or whatever) so we
  # only compare the sizes to make sure they are similar
  compare_size zstd_file.zst cmprss_file.zst
  # Decompress the 4 variations
  echo "Decompressing"
  zstd -d -c zstd_file.zst >zstd_zstd
  zstd -d -c cmprss_file.zst >cmprss_zstd
  cmprss zstd --extract cmprss_file.zst cmprss_cmprss --progress=off
  cmprss zstd --extract zstd_file.zst zstd_cmprss --progress=off
  echo "Comparing the decompressed files"
  compare file zstd_zstd
  compare file zstd_cmprss
  compare file cmprss_cmprss
  compare file cmprss_zstd
  echo "No errors detected"
}

test_zstd() {
  test_zstd_level 1 # Fast compression
  test_zstd_level 3
  test_zstd_level 6 # Default
  test_zstd_level 9 # High compression
}

# Test lz4 compression
test_lz4() {
  tmpdir
  echo "Testing lz4 in $PWD"
  echo "Creating random data"
  random_file 1000000 file
  echo "Compressing with lz4 and cmprss"
  lz4 -c file >lz4_file.lz4
  cmprss lz4 file cmprss_file.lz4 --progress=off
  # Compare the two archives
  # The archives may have slight variations (versioning or whatever) so we
  # only compare the sizes to make sure they are similar
  compare_size lz4_file.lz4 cmprss_file.lz4
  # Decompress the 4 variations
  echo "Decompressing"
  lz4 -d -c lz4_file.lz4 >lz4_lz4
  lz4 -d -c cmprss_file.lz4 >cmprss_lz4
  cmprss lz4 --extract cmprss_file.lz4 cmprss_cmprss --progress=off
  cmprss lz4 --extract lz4_file.lz4 lz4_cmprss --progress=off
  echo "Comparing the decompressed files"
  compare file lz4_lz4
  compare file lz4_cmprss
  compare file cmprss_cmprss
  compare file cmprss_lz4
  echo "No errors detected"
}

# Test lzma (legacy LZMA1) at the given level against the lzma CLI from xz-utils
test_lzma_level() {
  tmpdir
  echo "Testing lzma level $1 in $PWD"
  echo "Creating random data"
  random_file 1000000 file
  echo "Compressing with lzma and cmprss"
  lzma --stdout -"$1" file >lzma_file.lzma
  cmprss lzma --level "$1" file cmprss_file.lzma --progress=off
  compare_size lzma_file.lzma cmprss_file.lzma
  echo "Decompressing"
  lzma --stdout --decompress lzma_file.lzma >lzma_lzma
  lzma --stdout --decompress cmprss_file.lzma >cmprss_lzma
  cmprss lzma --extract cmprss_file.lzma cmprss_cmprss --progress=off
  cmprss lzma --extract lzma_file.lzma lzma_cmprss --progress=off
  echo "Comparing the decompressed files"
  compare file lzma_lzma
  compare file cmprss_lzma
  compare file cmprss_cmprss
  compare file lzma_cmprss
  echo "No errors detected"
}

test_lzma() {
  test_lzma_level 1
  test_lzma_level 6 # Default
  test_lzma_level 9
}

# Test brotli at the given quality against the brotli CLI
test_brotli_level() {
  tmpdir
  echo "Testing brotli quality $1 in $PWD"
  echo "Creating random data"
  random_file 1000000 file
  echo "Compressing with brotli and cmprss"
  brotli --quality="$1" --stdout file >brotli_file.br
  cmprss brotli --level "$1" file cmprss_file.br --progress=off
  compare_size brotli_file.br cmprss_file.br
  echo "Decompressing"
  brotli --decompress --stdout brotli_file.br >brotli_brotli
  brotli --decompress --stdout cmprss_file.br >cmprss_brotli
  cmprss brotli --extract cmprss_file.br cmprss_cmprss --progress=off
  cmprss brotli --extract brotli_file.br brotli_cmprss --progress=off
  echo "Comparing the decompressed files"
  compare file brotli_brotli
  compare file cmprss_brotli
  compare file cmprss_cmprss
  compare file brotli_cmprss
  echo "No errors detected"
}

test_brotli() {
  test_brotli_level 1
  test_brotli_level 6 # Default
  test_brotli_level 11
}

# Test framed snappy interop against the snzip CLI. snzip's default format is
# `framing2`, which is the same official framing format the `snap` crate uses
# (and that cmprss writes with the `.sz` extension). Snappy has no compression
# level, so no --level is passed.
test_snappy() {
  tmpdir
  echo "Testing snappy in $PWD"
  echo "Creating random data"
  random_file 1000000 file
  echo "Compressing with snzip and cmprss"
  snzip -c file >snzip_file.sz
  cmprss snappy file cmprss_file.sz --progress=off
  compare_size snzip_file.sz cmprss_file.sz
  echo "Decompressing"
  snzip -d -c snzip_file.sz >snzip_snzip
  snzip -d -c cmprss_file.sz >cmprss_snzip
  cmprss snappy --extract cmprss_file.sz cmprss_cmprss --progress=off
  cmprss snappy --extract snzip_file.sz snzip_cmprss --progress=off
  echo "Comparing the decompressed files"
  compare file snzip_snzip
  compare file cmprss_snzip
  compare file cmprss_cmprss
  compare file snzip_cmprss
  echo "No errors detected"
}

# Test tar archive interop with the tar CLI. Tar has no progress bar, so no
# --progress flag is passed.
test_tar() {
  tmpdir
  echo "Testing tar in $PWD"
  echo "Creating random data"
  random_dir 10 indir
  echo "Creating tar archives with each tool"
  tar -cf tar_archive.tar indir
  cmprss tar indir cmprss_archive.tar
  echo "Extracting each archive with the opposite tool"
  mkdir -p tar_from_cmprss
  tar -xf cmprss_archive.tar -C tar_from_cmprss
  mkdir -p cmprss_from_tar
  cmprss tar --extract tar_archive.tar cmprss_from_tar
  echo "Comparing the extracted contents"
  compare indir tar_from_cmprss/indir
  compare indir cmprss_from_tar/indir
  echo "No errors detected"
}

# Test zip archive interop with the zip/unzip CLIs. Zip has no progress bar,
# so no --progress flag is passed.
test_zip() {
  tmpdir
  echo "Testing zip in $PWD"
  echo "Creating random data"
  random_dir 10 indir
  echo "Creating zip archives with each tool"
  (cd "$(dirname indir)" && zip -qr zip_archive.zip "$(basename indir)")
  cmprss zip indir cmprss_archive.zip
  echo "Extracting each archive with the opposite tool"
  mkdir -p zip_from_cmprss
  (cd zip_from_cmprss && unzip -q ../cmprss_archive.zip)
  mkdir -p cmprss_from_zip
  cmprss zip --extract zip_archive.zip cmprss_from_zip
  echo "Comparing the extracted contents"
  compare indir zip_from_cmprss/indir
  compare indir cmprss_from_zip/indir
  echo "No errors detected"
}

# Test 7z archive interop with the 7z CLI (from p7zip). 7z has no --progress
# flag, so it isn't passed here. -bso0/-bsp0 silence 7z's output/progress noise.
test_7z() {
  tmpdir
  echo "Testing 7z in $PWD"
  echo "Creating random data"
  random_dir 10 indir
  echo "Creating 7z archives with each tool"
  7z a -bso0 -bsp0 sevenz_archive.7z indir
  cmprss 7z indir cmprss_archive.7z
  echo "Extracting each archive with the opposite tool"
  mkdir -p sevenz_from_cmprss
  7z x -bso0 -bsp0 "-osevenz_from_cmprss" cmprss_archive.7z
  mkdir -p cmprss_from_sevenz
  cmprss 7z --extract sevenz_archive.7z cmprss_from_sevenz
  echo "Comparing the extracted contents"
  compare indir sevenz_from_cmprss/indir
  compare indir cmprss_from_sevenz/indir
  echo "No errors detected"
}

# Shared helper for tar.<codec> pipeline interop. The first arg is the compound
# extension; the rest are the tar flags used to compress/extract that codec
# (e.g. `-z`, `--zstd`, or `-I lzma` for codecs without a short flag).
# Verifies that cmprss produces archives the tar CLI can read, and vice versa.
# Pipelines are invoked without a subcommand so --progress isn't accepted; the
# archive is written to a file, not stdout, which is fine for tests.
test_tar_pipeline() {
  local ext="$1"
  shift
  local tar_flags=("$@")
  tmpdir
  echo "Testing $ext pipeline in $PWD"
  echo "Creating random data"
  random_dir 10 indir
  echo "Creating $ext archives with each tool"
  tar "${tar_flags[@]}" -cf tar_archive."$ext" indir
  cmprss indir cmprss_archive."$ext"
  echo "Extracting each archive with the opposite tool"
  mkdir -p tar_from_cmprss
  tar "${tar_flags[@]}" -xf cmprss_archive."$ext" -C tar_from_cmprss
  mkdir -p cmprss_from_tar
  cmprss --extract tar_archive."$ext" cmprss_from_tar
  echo "Comparing the extracted contents"
  compare indir tar_from_cmprss/indir
  compare indir cmprss_from_tar/indir
  echo "No errors detected"
}

test_tar_gz() { test_tar_pipeline tar.gz -z; }
test_tar_xz() { test_tar_pipeline tar.xz -J; }
test_tar_bz2() { test_tar_pipeline tar.bz2 -j; }
test_tar_zst() { test_tar_pipeline tar.zst --zstd; }
# tar has no short flag for these codecs; drive the external CLI via -I.
test_tar_lzma() { test_tar_pipeline tar.lzma -I lzma; }
test_tar_br() { test_tar_pipeline tar.br -I brotli; }
test_tar_lz4() { test_tar_pipeline tar.lz4 -I lz4; }
test_tar_sz() { test_tar_pipeline tar.sz -I snzip; }

# Run all the tests if no arguments are given
if [ $# -eq 0 ]; then
  set -- gzip xz bzip2 zstd lz4 lzma brotli snappy tar zip 7z \
    tar_gz tar_xz tar_bz2 tar_zst tar_lzma tar_br tar_lz4 tar_sz
fi

# Run the tests given on the command line
for test in "$@"; do
  test_"$test"
done
