#!/usr/bin/env bash

# A little helper for copying built tests into an implementation's repo, for
# testing changes.
#
# The destination directory is specified with `-d`, `--dest` or `--destination`.
# Source files can be positional (in which case, they are ignored if not found
# to be relative to the test directory), or specified via utilities such as
# `--head` (tests changed in the current working directory), or `--since N`
# (tests changed in the last N commits).

script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
root_dir="$( dirname "$script_dir" )"
tests_dir="$root_dir/test"

help_msg="$(cat <<-END

Help:

    -d <argument>             | -d=<argument>
    --dest <argument>         | --dest=<argument>
    --destination <argument>  | --destination=<argument>

        Specify a destination directory to receive copied tests. If not present,
        the script runs in dry-run mode and does not copy files.

    -n
    --dryrun
    --dry-run

        Don't copy files, but print the operations which would be performed.
        Useful for debugging.

    --head

        Copy test files which have changed in the working directory since the
        last commit. Performs \`git diff HEAD --name-only\`.

    -s <argument>             | -s=<argument>
    --diff <argument>         | --diff=<argument>
    --since <argument>        | --since=<argument>

        Copy files which have changed in the past N commits. Also includes
        changes in the current working directory since the last commit. Performs
        \`git diff HEAD~\<argument> --name-only\`.

    -h
    --help

        Print this help message.

END
)"

check_path() {
    local file="$1"
    if ( [ ! -f "$file" ] ); then
        return
    fi

    local path=$(echo "$( realpath --relative-to="$root_dir/test" "$file" )" | tr "/" " ")
    for part in $path; do
        if [ "$part" = ".." ]; then
            return
        fi
    done

    echo "$file"
}

files=( )
dry_run=0
while [[ $# -gt 0 ]]; do
    case "$1" in
        -d|--dest|--destination)
            # Select the destination to copy into (value is following
            # positional parameter)
            destination="$2"
            shift
            shift
            ;;
        -d=*|--dest=*|--destination=*)
            # Select the destination to copy into (value follows `=` sign)
            destination="${i#*=}"
            shift
            ;;
        -n|--dryrun|--dry-run)
            # Don't actually copy files, but instead list the files to copy
            dry_run=1
            shift
            ;;
        --head)
            # Add test files changed since the last commit
            files+=( "$( git diff HEAD --name-only --diff-filter=d | grep "^test/" )" )
            shift
            ;;
        -s|--diff|--since)
            # Add test files changed in the last N commits (N is following
            # positional parameter)
            files+=( "$( git diff HEAD~$2 --name-only --diff-filter=d | grep "^test/" )" )
            shift
            shift
            ;;
        -s=*|--diff=*|--since=*)
            # Add test files changed in the last N commits (N follows `=` sign)
            files+=( "$( git diff HEAD~${i#*=} --name-only --diff-filter=d | grep "^test/" )" )
            shift
            ;;
        -h|--help)
            echo -n "$help_msg"
            exit 0
            ;;
        *)
            # Add a specific test, if it's a file relative to $tests_dir
            if [ -n "$( check_path "$1" )" ]; then
              files+=( "$1" )
            fi
            shift
            ;;
    esac
done

if [ ${#destination} -eq 0 ]; then
  echo -n "Error: No destination specified."
  echo -n "$help_msg"
  exit 1
fi

function main {
    for file in $files; do
        # Convert absolute path to relative path
        file="$( realpath --relative-to="$root_dir" "$file" )"
        target_path="$destination/$( dirname "$file" )"
        cmd=$( cat <<-END
mkdir -p "$target_path"
cp -f "$file" "$target_path"

END
)
        if [ $dry_run -eq 0 ]; then
          eval "$cmd"
        else
          echo "$cmd"
          echo ""
        fi
    done
}

old_dir=$( pwd )
cd "$root_dir"
main
cd "$old_dir"