#!@BASH@

# Driver for rastertogutenprint tester.
#
# Copyright 2007-2017 Robert Krawitz (rlk@alum.mit.edu)
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

retval=0

if [[ -z $srcdir || $srcdir = . ]] ; then
    sdir=$(pwd)
elif [[ $srcdir =~ ^/ ]] ; then
    sdir="$srcdir"
else
    sdir="$(pwd)/$srcdir"
fi

export STP_DATA_PATH=${STP_DATA_PATH:-"$sdir/../xml"}
export STP_MODULE_PATH=${STP_MODULE_PATH:-"$sdir/../main:$sdir/../main/.libs"}

declare single=0
declare verbose=0
declare valgrind=0
declare make_ppds=1
declare skip_simplified=0
declare postscript=0
declare use_explicit_quality=0
declare extra_genppd_opts="-Z"
declare use_smallest_pagesize=0
declare cupsargs=''
declare -a printers_to_test
declare dontrun=0

if [ -n "$STP_TEST_DEBUG" ] ; then
    echo "Would run with single=$single skip_simplified=$skip_simplified extra_genppd_opts=$extra_genppd_opts cupsargs=$cupsargs use_explicit_quality=$use_explicit_quality valopts=$valopts valgrind=$valgrind"
    exit 0
fi

declare family=$STP_TEST_FAMILY
declare all_models=''
declare md5dir=''
declare outdir=''
declare qualarg=''
declare npages=3
declare jobs=${STP_PARALLEL:-1}
declare -A all_models=

if [[ -r $sdir/../../src/cups/gutenprint-users-manual.pdf ]] ; then
    testfile="$sdir/../../src/cups/gutenprint-users-manual.pdf"
else
    testfile="$sdir/../../doc/gutenprint-users-manual.pdf"
fi

quality_presets=(FastEconomy Economy Draft Standard High Photo HighPhoto UltraPhoto Best)

usage() {
    cat <<'EOF'
Usage: test-rastertogutenprint [options] [PPD files...]
  Options:
    -s        Run only one PPD file with a given model ID/family
    -v        Use valgrind
    -c        Use cachegrind
    -g        Use GDB attach in valgrind
    -V        Verbose output
    -n        Don't build PPD files prior to run
    -O dir    Save output in specified directory
    -o opt    Set option on CUPS command line
    -m dir    Save MD5 checksums in specified directory
    -p pages  Specify page range of input document to use
    -P        Use PostScript rather than PDF input
    -t jobs   Run jobs in parallel (alternatively, use STP_PARALLEL)
    -f family Run printers only in the particular family
    -S        Skip simplified PPD files
    -l        Use lowest available quality setting
    -L        Use highest available quality setting
    -X        Don't use explicit quality setting
    -N	      Use the smallest available page size
    -d	      Don't actually run the command
EOF
exit 0;
}

while getopts "hvcgsVnO:m:o:p:PSt:lLXf:Nd" opt ; do
    case "$opt" in
	h*) usage ;;
	v) valgrind=$((valgrind + 1)) ;;
	c) valgrind=4 ;;
	g) valopts='--vgdb=yes --error-exitcode=1' ;;
	s) single=1 ;;
	V) verbose=$((verbose+1)) ;;
	n) make_ppds=0 ;;
	O) outdir="$OPTARG"; mkdir -p "$outdir" ;;
	o) cupsargs="$cupsargs $OPTARG" ;;
	m) md5dir="$OPTARG"; mkdir -p "$md5dir" ;;
	p) npages="$OPTARG" ;;
	P) postscript=1 ;;
	t) jobs="$OPTARG" ;;
	f) family="$OPTARG" ;;
	S) skip_simplified=1 ;;
	X) use_explicit_quality=0 ;;
	l) use_explicit_quality=1 ;;
	L) use_explicit_quality=2 ;;
	N) use_smallest_pagesize=1 ;;
	d) dontrun=1 ;;
	\?) usage ;;
	*) echo "Unknown argument $opt"; usage ;;
    esac
done

STP_TEST_ROTOR=${STP_TEST_ROTOR:-0}
STP_TEST_ROTOR_CIRCUMFERENCE=${STP_TEST_ROTOR_CIRCUMFERENCE:-1}

case "$valgrind" in
    4)
	valopts='--tool=callgrind --dump-instr=yes --trace-jump=yes --error-exitcode=1'
	;;
    '')
	;;
    *)
	valopts='--tool=memcheck --error-exitcode=1'
	;;
esac

shift $((OPTIND - 1))

if (( $# > 0 )) ; then
    single=0
    for i in $(seq 1 $#) ; do
	(( (i - 1) % $STP_TEST_ROTOR_CIRCUMFERENCE == $STP_TEST_ROTOR )) && printers_to_test+=($1)
	shift
    done
    (( ${#printers_to_test[@]} == 0 )) && exit 77
fi

(( single > 0 )) && extra_genppd_opts="$extra_genppd_opts -S"

if (( STP_TEST_ROTOR_CIRCUMFERENCE > 1 &&
	  STP_TEST_ROTOR >= 0 &&
	  STP_TEST_ROTOR < STP_TEST_ROTOR_CIRCUMFERENCE )) ; then
    extra_genppd_opts="$extra_genppd_opts -R $STP_TEST_ROTOR_CIRCUMFERENCE -r $STP_TEST_ROTOR"
    STP_TEST_ROTOR=0
    STP_TEST_ROTOR_CIRCUMFERENCE=1
fi

version="@GUTENPRINT_RELEASE_VERSION@";
rgp="./rastertogutenprint.$version"
cupsdir="$(cups-config --serverbin)/filter"
cgpdftoraster="$cupsdir/cgpdftoraster"
gstoraster="$cupsdir/gstoraster"
imagetoraster="$cupsdir/imagetoraster"
pdftops="$cupsdir/pdftops"
pstops="$cupsdir/pstops"
pstoraster="$cupsdir/pstoraster"

if [[ ! -x $cgpdftoraster && ! -x $pdftops && ! -x $gstoraster ]] ; then
    echo 'CUPS does not appear to be installed, skipping test'
    exit 0
fi

if [[ -x $pstoraster || -x $gstoraster || -x $cgpdftoraster ]] ; then
    pages="24-$((24 + npages - 1))"
    (( postscript > 0 )) && pages="page-ranges=$pages"
else
    pages=''
fi

cleanup() {
    [[ -n $tfile ]] && rm -f "$tfile"
    exit 1
}

pdfjam=$(type -p pdfjam)
[[ -z $pdfjam ]] && postscript=1

if (( postscript > 0 )) ; then
    pdftops=$(type -p pdftops)

    if [[ -n $pdftops && ! -x $cgpdftoraster ]] ; then
	tfile=$(mktemp)
	trap cleanup 1 2 3 6 14 15 30
	"$pdftops" -f 24 -l $((24 + npages - 1)) "$testfile" "$tfile"
    fi
else
    tfile=$(mktemp)
    trap cleanup 1 2 3 6 14 15 30
    "$pdfjam" -q "$testfile" "$pages" -o "$tfile"
fi

case "$verbose" in
    1)
	export STP_SUPPRESS_VERBOSE_MESSAGES=1
	;;
    0|'')
	export STP_SUPPRESS_MESSAGES=1
	export STP_SUPPRESS_VERBOSE_MESSAGES=1
	;;
    *)
	;;
esac

# Note that using CUPS arguments may trigger valgrind memory leaks in
# CUPS.
#cupsargs='PageSize=Custom.400.00x500.00'
#cupsargs='PageSize=Custom.324x495 Resolution=180dpi'
#cupsargs='PageSize=w324h495 Resolution=180dpi'
#cupsargs='PageSize=A8'

get_ppds() {
    if [[ -n $* ]] ; then
	for f in "$@" ; do
	    if [[ -r $f ]] ; then
		echo "$f"
	    elif [[ -r ppd/C/$f ]] ; then
		echo "ppd/C/$f"
	    elif [[ -f ppd/C/${f}.ppd ]] ; then
		echo "ppd/C/${f}.ppd"
	    elif [[ -f ppd/C/${f}.ppd.gz ]] ; then
		echo "ppd/C/${f}.ppd.gz"
	    elif [[ -f ppd/C/${f}.ppd.GZ ]] ; then
		echo "ppd/C/${f}.ppd.GZ"
	    elif [[ -f ppd/C/${f}.ppd.bz2 ]] ; then
		echo "ppd/C/${f}.ppd.bz2"
	    elif [[ -f ppd/C/${f}.ppd.BZ2 ]] ; then
		echo "ppd/C/${f}.ppd.BZ2"
	    elif [[ -f ppd/C/${f}.ppd.z ]] ; then
		echo "ppd/C/${f}.ppd.z"
	    elif [[ -f ppd/C/${f}.ppd.Z ]] ; then
		echo "ppd/C/${f}.ppd.Z"
	    elif [[ -f ppd/C/stp-${f}.ppd ]] ; then
		echo "ppd/C/stp-${f}.ppd"
	    elif [[ -f ppd/C/stp-${f}.ppd.gz ]] ; then
		echo "ppd/C/stp-${f}.ppd.gz"
	    elif [[ -f ppd/C/stp-${f}.ppd.GZ ]] ; then
		echo "ppd/C/stp-${f}.ppd.GZ"
	    elif [[ -f ppd/C/stp-${f}.ppd.bz2 ]] ; then
		echo "ppd/C/stp-${f}.ppd.bz2"
	    elif [[ -f ppd/C/stp-${f}.ppd.BZ2 ]] ; then
		echo "ppd/C/stp-${f}.ppd.BZ2"
	    elif [[ -f ppd/C/stp-${f}.ppd.z ]] ; then
		echo "ppd/C/stp-${f}.ppd.z"
	    elif [[ -f ppd/C/stp-${f}.ppd.Z ]] ; then
		echo "ppd/C/stp-${f}.ppd.Z"
	    elif [[ -f ppd/C/stp-${f}.${version}.ppd ]] ; then
		echo "ppd/C/stp-${f}.${version}.ppd"
	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.gz ]] ; then
		echo "ppd/C/stp-${f}.${version}.ppd.gz"
	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.GZ ]] ; then
		echo "ppd/C/stp-${f}.${version}.ppd.GZ"
	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.bz2 ]] ; then
		echo "ppd/C/stp-${f}.${version}.ppd.bz2"
	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.BZ2 ]] ; then
		echo "ppd/C/stp-${f}.${version}.ppd.BZ2"
	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.z ]] ; then
		echo "ppd/C/stp-${f}.${version}.ppd.z"
	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.Z ]] ; then
		echo "ppd/C/stp-${f}.${version}.ppd.Z"
	    fi
	done
    else
	echo ppd/C/*.ppd*
    fi
}

if [[ $make_ppds -gt 0 || ! -d ppd/C ]] ; then
    rm -rf ppd/C
## not all systems can work with gzipped PPDs
    extra_genppd_opts="$extra_genppd_opts ${printers_to_test[*]}"
    if [[ $skip_simplified == 1 ]] ; then
        echo make ppd-nonls EXTRA_GENPPD_OPTS="$extra_genppd_opts"
        make ppd-nonls EXTRA_GENPPD_OPTS="$extra_genppd_opts"
    else
        make ppd-nonls-a EXTRA_GENPPD_OPTS="$extra_genppd_opts"
    fi
fi

find_page_size() {
    ppd=$1
    (( use_smallest_pagesize == 0 )) && return;
    driver=$(grep '^\*StpDriverName' "$ppd" | sed -e 's/^[^"]*"//' -e 's/"//g')
    pagesize=$(./min-pagesize "$driver")
    [[ -n "$pagesize" ]] && echo "PageSize=$pagesize"
}

find_resolution() {
    ppd=$1
    resolutions=$(grep "^\\*Resolution " "$ppd" |sed -e 's,/.*,,' -e 's/.* //')
    [[ -z "$resolutions" ]] && return
    low_resolution=9999999999
    low_resolution_name=''
    high_resolution=0
    high_resolution_name=''
    for r in $resolutions ; do
	res=$(sed -e 's/dpi//' -e 's/x/ \\\* /' -e 's/^\([0-9]*\)$/\1 \\\* \1/' <<< "$r")
	resnum=$(eval "expr $res")
	if (( resnum > high_resolution )) ; then
	    high_resolution=$resnum
	    high_resolution_name=$r
	fi
	if (( resnum < low_resolution )) ; then
	    low_resolution=$resnum
	    low_resolution_name=$r
	fi
    done
    if (( use_explicit_quality == 1 )) ; then
	echo "Resolution=$low_resolution_name"
    elif (( use_explicit_quality == 2 )) ; then
	echo "Resolution=$high_resolution_name"
    fi

}

find_quality_preset() {
    ppd=$1
    if (( use_explicit_quality == 1 )) ; then
	for q in "${quality_presets[@]}" ; do
	    if grep -q "^\\*StpQuality $q" "$ppd" ; then
		echo "StpQuality=$q"
		return
	    fi
	done
    elif (( use_explicit_quality == 2 )) ; then
	best_quality=''
	for q in "${quality_presets[@]}" ; do
	    grep -q "^\\*StpQuality $q" "$ppd" && best_quality=$q
	done
	[[ -n $best_quality ]] && echo "StpQuality=$best_quality"
    fi
}

find_quality() {
    ppd=$1
    if [[ ! -r $ppd ]] ; then
	echo "Can't find $ppd!" 1>&2
	exit 1;
    fi
    (( use_explicit_quality == 0 )) && return
    if grep -q '\*Resolution' "$ppd" ; then
	find_resolution "$ppd"
    else
	find_quality_preset "$ppd"
    fi
}

xgrep() {
    pat=$1
    file=$2
    if [[ $file == *.gz ]] ; then
	grep -E -m1 "$pat" "$file"
    else
	zgrep "$pat" "$file"
    fi
}

runcmd() {
    qualarg=$(find_quality "$PPD")
    sizearg=$(find_page_size "$PPD")
    a=(1 1 1 1)
    qarg="$qualarg $sizearg $cupsargs"
    if [[ -x $cgpdftoraster ]] ; then
	# cgpdftoraster doesn't like arguments.  How rude.
	$cgpdftoraster "${a[@]}" "" < "$tfile"
    elif [[ -f $tfile && -x $gstoraster ]] ; then
	$gstoraster "${a[@]}" "$qarg" < "$tfile"
    elif [[ -f $tfile ]] ; then
	$pstops "${a[@]}" $"qarg" < "$tfile"
    elif [[ -x $pstoraster ]] ; then
	$pdftops "${a[@]}" "$qarg" < "$tfile" | $pstops "${a[@]}" "$pages$qarg" | $pstoraster
    elif [[ -x $gstoraster ]] ; then
	$pdftops "${a[@]}" "$qarg" < "$tfile" | $gstoraster "${a[@]}" "$pages$qarg"
    else
	$imagetoraster "${a[@]}" "$qarg" < calibrate.ppm
    fi
}

do_output() {
    driver=$(xgrep '^\*StpDriverName:' "$PPD" |awk '{print $2}' | sed 's/"//g')
    if [[ -n $outdir ]] ; then
	cat > "$outdir/$driver.prn"
	if [[ -n $md5dir ]] ; then
	    md5sum < "$outdir/$driver.prn" | sed "s/-/\*$driver/" > "$md5dir/$driver.md5"
	fi
    elif [[ -n $md5dir ]] ; then
	cat | md5sum | sed "s/-/\*$driver/" > "$md5dir/$driver.md5"
    else
	cat >/dev/null
    fi
}

run_rastertogp() {
    qualarg=$(find_quality "$PPD")
    sizearg=$(find_page_size "$PPD")
    vg="libtool --mode=execute valgrind $valopts --log-fd=3"
    vg1="$vg --num-callers=50 --leak-check=yes --error-limit=no --error-exitcode=1"
    rgpc="$rgp 1 1 1 1"
    qarg="$qualarg $sizearg $cupsargs"
    case "$valgrind" in
	1) $vg1 -q $rgpc ;;
	2) $vg1 --leak-resolution=high $rgpc "$qarg" ;;
	3) $vg1 --leak-resolution=high --show-reachable=yes $rgpc "$qarg";;
	4) $vg $rgpc "$qarg" ;;
	5) cat ;;
	6) cat > /dev/null ;;
	*) $rgpc "$qarg"
    esac
}

runme() {
    f="$1"
    m=${all_models[$f]}
    p=${f#*stp-}
    p=${p/${version}./}
    export PPD=$f
    if (( dontrun > 0 )) ; then
	output="${p%.ppd*} ($m)"
    elif [[ -n $outdir || -n $md5dir ]] ; then
	output="${p%.ppd*} ($m)...$( (runcmd 2>/dev/null | run_rastertogp | do_output) 2>&1 3>&2)"
    else
	output="${p%.ppd*} ($m)...$( (runcmd 2>/dev/null | run_rastertogp >/dev/null) 2>&1 3>&2)"
    fi
    return $?
}

runall() {
    jobs="${1:-1}"
    rotor="${2:-0}"
    shift 2
    retval=0
    jobno=0
    for f in "$@" ; do
	if (( jobno == rotor )) ; then
	    runme "$f" || retval=1
	    echo "$output"
	    grep -q 'ERROR:' <<< "$output" && retval=1
	fi
	jobno=$(((jobno+1) % jobs))
    done
    return $retval
}

get_models() {
    re='\*StpDriverModelFamily:	'
    if (( ${#all_models[*]} <= 1 )) ; then
	declare -a models=($(xargs grep -m1 -H "^$re" <<< "$*" | sed "s/:$re/=/"))
	for m in "${models[@]}" ; do
	    model=${m#*=}
	    file=${m%%=*}
	    all_models[$file]=$model
	done
    fi
}

retval=0
if [[ -d ppd/C ]] ; then
    declare -a files=($(get_ppds))
    declare -A models
    declare -a nfiles
    if (( skip_simplified > 0 )) ; then
	for f in "${files[@]}" ; do
	    [[ $f != *.sim.ppd* ]] && nfiles+=("$f")
	done
	files=("${nfiles[@]}")
    fi
    get_models "${files[@]}"
    if [[ -n $family ]] ; then
	nfiles=()
	for f in "${files[@]}" ; do
	    [[ ${all_models[$f]} =~ $family ]] && nfiles+=("$f")
	done
	files=("${nfiles[@]}")
    fi
    if (( single != 0 )) ; then
	declare -A seen_models
	nfiles=()
	get_models "${files[@]}"
	for f in "${files[@]}" ; do
	    model=${all_models[$f]}
	    [[ $f == *.sim.ppd ]] && model="${model}_sim"
	    if [[ -z ${seen_models[$model]} ]] ; then
		nfiles+=("$f")
		seen_models[$model]=1
	    fi
	done
	files=("${nfiles[@]}")
    fi
    (( ${#files[@]} < jobs )) && jobs=${#files[@]}
    # Fire 'em off in the background...
    for i in $(seq 0 $((jobs-1))) ; do
	runall "$jobs" "$i" "${files[@]}" &
    done
    # And wait for them to complete.
    for i in $(seq 0 $((jobs-1))) ; do
	wait -n || retval=1
    done
fi

[[ -n $tfile ]] && rm -f "$tfile"
(( retval == 0 && make_ppds > 0 )) && rm -rf ppd/C && rmdir ppd
exit "$retval"
