#!/bin/sh
#                   C O N V E R S I O N . S H
# BRL-CAD
#
# Copyright (c) 2010-2021 United States Government as represented by
# the U.S. Army Research Laboratory.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# 3. The name of the author may not be used to endorse or promote
# products derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
###
#
# Check the status of our conversion capability on given geometry
#
###

# Ensure /bin/sh
export PATH || (echo "This isn't sh."; sh $0 $*; kill $$)

# save the precious args
ARGS="$*"
NAME_OF_THIS=`basename $0`
PATH_TO_THIS=`dirname $0`
THIS="$PATH_TO_THIS/$NAME_OF_THIS"

# sanity check
if test ! -f "$THIS" ; then
    echo "INTERNAL ERROR: $THIS does not exist"
    if test ! "x$0" = "x$THIS" ; then
	echo "INTERNAL ERROR: dirname/basename inconsistency: $0 != $THIS"
    fi
    exit 1
fi

# force locale setting to C so things like date output as expected
LC_ALL=C

# force posix behavior
POSIX_PEDANTIC=1
POSIXLY_CORRECT=1
export POSIX_PEDANTIC POSIXLY_CORRECT


#######################
# log to tty and file #
#######################
log ( ) {

    # this routine writes the provided argument(s) to a log file as
    # well as echoing them to stdout if we're not in quiet mode.

    if test $# -lt 1 ; then
	format=""
    else
	format="$1"
	shift
    fi

    if test ! "x$LOGFILE" = "x" ; then
	action="printf \"$format\n\" $* >> \"$LOGFILE\""
	eval "$action"
    fi
    if test ! "x$QUIET" = "x1" ; then
	action="printf \"$format\n\" $*"
	eval "$action"
    fi
}


##############################
# ensure a 0|1 boolean value #
##############################
booleanize ( ) {

    # this routine examines the value of the specified variable and
    # converts it to a simple zero or one value in order to simplify
    # checks later on

    for var in $* ; do

	VAR="$var"
	VALCMD="echo \$$VAR"
	VAL="`eval $VALCMD`"

	CMD=__UNSET__
	case "x$VAL" in
	    x)
		CMD="$VAR=0"
		;;
	    x0)
		CMD="$VAR=0"
		;;
	    x[nN]*)
		CMD="$VAR=0"
		;;
	    x*)
		CMD="$VAR=1"
		;;
	esac

	if test ! "x$CMD" = "x__UNSET__" ; then
	    eval $CMD
	    export $VAR
	fi

    done
}


################
# elapsed time #
################
elapsed ( ) {

    # this routine either reports the current time (in seconds)

    elp=`date '+%s'`
    echo $elp
    return
}


####################
# handle arguments #
####################

# process the argument list for commands
while test $# -gt 0 ; do
    arg="$1"
    case "x$arg" in
	x*[hH])
	    HELP=1
	    ;;
	x*[hH][eE][lL][pP])
	    HELP=1
	    ;;
	x*[iI][nN][sS][tT][rR][uU][cC][tT]*)
	    INSTRUCTIONS=1
	    ;;
	x*[qQ][uU][iI][eE][tT])
	    QUIET=1
	    ;;
	x*[vV][eE][rR][bB][oO][sS][eE])
	    VERBOSE=1
	    ;;
	x*[kK][eE][eE][pP])
	    KEEP=1
	    ;;
	x*=*)
	    VAR=`echo $arg | sed 's/=.*//g' | sed 's/^[-]*//g'`
	    if test ! "x$VAR" = "x" ; then
		VAL=`echo $arg | sed 's/.*=//g'`
		CMD="$VAR=\"$VAL\""
		eval $CMD
		export $VAR
	    fi
	    ;;
	x*)
	    # should be geometry database files, so stop
	    break
	    ;;
    esac
    shift
done

# validate and clean up options (all default to 0)
booleanize HELP INSTRUCTIONS VERBOSE KEEP


###
# handle instructions before main processing
###
if test "x$INSTRUCTIONS" = "x1" ; then
    cat <<EOF

This script is used to test BRL-CAD's explicit surface polygonal
conversion capabilities.  It takes a list of one or more geometry
files and for every geometry object in the file, it facetizes the
object into NMG and BoT format.  The script times, tabulates, and
reports conversion results.

The script is useful for a variety of purposes including for
developers to perform regression testing as BRL-CAD's conversion
capabilities are modified and improved.  It's also useful for users to
get a report of which objects in a given BRL-CAD geometry database
will convert, what percentage, and how long the conversion will take.

There are several environment variables that will modify how this
script behaves:

  GED          - file path to geometry editor to use for converting
  SEARCH       - file to geometry editor to use for searching
  OPATH        - geometry path to use for object search (default .)
  OBJECTS      - parameters for selecting objects to convert
  KEEP         - retain the converted geometry instead of deleting
  MAXTIME      - maximum number of seconds allowed for each conversion
  QUIET        - turn off all output (writes results to log file)
  VERBOSE      - turn on extra debug output for testing/development
  INSTRUCTIONS - display these more detailed instructions

The GED option allows you to specify a specific pathname for MGED.
The default is to search the system path for 'mged'.

The SEARCH option allows you to specify a different MGED that will be
used for finding objects.  As MGED's "search" command was added in
release 7.14.0, this option allows you to find objects using a 7.14.0+
version of MGED but then attempt to convert geometry with a
potentially older version of MGED.  The default is to use the same
mged (specified by the GED option) being used for conversions.

The OBJECTS option allows you to specify which objects you want to
convert.  Any of the parameters recognized by the GED 'search' command
can be used.  See the 'search' manual page for details on all
available parameters.  Examples:

OBJECTS="-type region"  # only convert regions
OBJECTS="-not -type comb"  # only convert primitives

The KEEP option retains the converted geometry file after conversion
processing has ended.  The default is to delete the working copy.

The MAXTIME option specifies how many seconds are allowed to elapse
before the conversion is aborted.  Some conversions can take days or
months (or infinite in the case of a bug), so this places an upper
bound on the per-object conversion time.
EOF
    exit 0
fi


if test $# -eq 0 ; then
    echo "No geometry files specified."
    echo ""
    HELP=1
fi

###
# handle help before main processing
###
if test "x$HELP" = "x1" ; then
    echo "Usage: $0 [command(s)] [OPTION=value] file1.g [file2.g ...]"
    echo ""
    echo "Available commands:"
    echo "  help          (this is what you are reading right now)"
    echo "  instructions"
    echo "  quiet"
    echo "  verbose"
    echo ""
    echo "Available options:"
    if test "x$GED" = "x" ; then
	echo "  GED=/path/to/mged (default mged)"
    else
	echo "  GED=/path/to/mged (using $GED)"
    fi
    if test "x$SEARCH" = "x" ; then
	echo "  SEARCH=/path/to/search-enabled/mged (default mged)"
    else
	echo "  SEARCH=/path/to/search-enabled/mged (using $SEARCH)"
    fi
    if test "x$OPATH" = "x" ; then
	echo "  OPATH=/path/to/objects (default .)"
    else
	echo "  OPATH=/path/to/objects (using $OPATH)"
    fi
    if test "x$OBJECTS" = "x" ; then
	echo "  OBJECTS=\"search params\" (default \"\" for all objects)"
    else
	echo "  OBJECTS=\"search params\" (using \"$OBJECTS\")"
    fi
    if test "x$KEEP" = "x" ; then
	echo "  KEEP=boolean (default \"no\")"
    else
	echo "  KEEP=boolean (using \"$KEEP\")"
    fi
    if test "x$MAXTIME" = "x" ; then
	echo "  MAXTIME=#seconds (default 300)"
    else
	echo "  MAXTIME=#seconds (using $MAXTIME)"
    fi
    echo ""
    echo "BRL-CAD is a powerful cross-platform open source solid modeling system."
    echo "For more information about BRL-CAD, see http://brlcad.org"
    echo ""
    echo "Run '$0 instructions' for additional information."
    exit 1
fi


#############
# B E G I N #
#############

# where to write results
LOGFILE=conversion-$$-run.log
touch "$LOGFILE"
if test ! -w "$LOGFILE" ; then
    if test ! "x$LOGFILE" = "x/dev/null" ; then
	echo "ERROR: Unable to log to $LOGFILE"
    fi
    LOGFILE=/dev/null
fi

VERBOSE_ECHO=:
ECHO=log
if test "x$QUIET" = "x1" ; then
    if test "x$VERBOSE" = "x1" ; then
	echo "Verbose output quelled by quiet option.  Further output disabled."
    fi
else
    if test "x$VERBOSE" = "x1" ; then
	VERBOSE_ECHO=echo
	echo "Verbose output enabled"
    fi
fi

if test "x$KEEP" = "x1" ; then
    $VERBOSE_ECHO "Converted geometry file will be retained"
fi

###
# ensure variable is set to something
###
set_if_unset ( ) {
    set_if_unset_name="$1" ; shift
    set_if_unset_val="$1" ; shift

    set_if_unset_var="echo \"\$$set_if_unset_name\""
    set_if_unset_var_val="`eval ${set_if_unset_var}`"
    if test "x${set_if_unset_var_val}" = "x" ; then
	set_if_unset_var="${set_if_unset_name}=\"${set_if_unset_val}\""
	eval $set_if_unset_var
	export $set_if_unset_name
    fi

    set_if_unset_var="echo \"\$$set_if_unset_name\""
    set_if_unset_val="`eval ${set_if_unset_var}`"
}

# approximate maximum time in seconds that a given conversion is allowed to take
set_if_unset GED mged
set_if_unset SEARCH $GED
set_if_unset MAXTIME 300

# commands that this script expects, make sure we can find MGED
MGED="`which $GED`"
if test ! -f "$MGED" ; then
    echo "ERROR: Unable to find $GED"
    echo ""
    echo "Configure the GED variable or check your PATH."
    echo "Run '$0 instructions' for additional information."
    echo ""
    echo "Aborting."
    exit 1
fi
SGED="`which $SEARCH`"
if test ! -f "$SGED" ; then
    echo "ERROR: Unable to find $SEARCH"
    echo ""
    echo "Configure the SEARCH variable or check your PATH."
    echo "Run '$0 instructions' for additional information."
    echo ""
    echo "Aborting."
    exit 1
fi


################
# start output #
################

$ECHO "B R L - C A D   C O N V E R S I O N"
$ECHO "==================================="
$ECHO "Running $THIS on `date`"
$ECHO "Logging output to $LOGFILE"
$ECHO "`uname -a 2>&1`"
$ECHO "Using [${MGED}] for GED"
$ECHO "Using [${MAXTIME}] for MAXTIME"
$ECHO

# iterate over every specified geometry file
files=0
count=0
nmg_count=0
bot_count=0
brep_count=0
$ECHO "%s" "-=-"
begin=`elapsed` # start elapsed runtime timer
while test $# -gt 0 ; do
    file="$1"
    if ! test -f "$file" ; then
	echo "Unable to read file [$file]"
	shift
	continue
    fi

    work="${file}.conversion.g"
    cmd="cp \"$file\" \"$work\""
    $VERBOSE_ECHO "\$ $cmd"
    eval "$cmd"

    # execute in a coprocess
    if test "x$OBJECTS" = "x" ; then OBJECTS='-print' ; fi
    cmd="$SGED -c \"$work\" search $OPATH $OBJECTS"
    objects=`eval "$cmd" 2>&1 | grep -v Using`
    $VERBOSE_ECHO "\$ $cmd"
    $VERBOSE_ECHO "$objects"

    # stash stdin on fd3 and set stdin to our object list.  this is
    # necessary because if a kill timer is called, input being read on
    # the while loop will be terminated early.
    exec 3<&0
    exec 0<<EOF
$objects
EOF

    # iterate over every geometry object in the current file
    while read object ; do

	obj="`basename \"$object\"`"
	found=`$SGED -c "$work" search . -name \"${obj}\" 2>&1 | grep -v Using`
	if test "x$found" != "x$object" ; then
	    $ECHO "INTERNAL ERROR: Failed to find [$object] with [$obj] (got [$found])"
	    continue
	fi

	# start the limit timer.  this will kill the upcoming facetize
	# job if more than MAXTIME seconds have elapsed.  in order for
	# this to work while still ECHO'ing a message and without
	# leaving orphaned 'sleep' processes that accumulate, this
	# method had to be executed in the current shell environment.

	{ sleep $MAXTIME && test "x`ps auxwww | grep "$work" | grep facetize | grep "${obj}.nmg" | awk '{print $2}'`" != "x" && touch "./${obj}.nmg.extl" && kill -9 `ps auxwww | grep "$work" | grep facetize | grep "${obj}.nmg" | awk '{print $2}'` 2>&4 & } 4>&2 2>/dev/null
	spid=$!

	# convert NMG
	nmg=fail
	cmd="$GED -c \"$work\" facetize -n \"${obj}.nmg\" \"${obj}\""
	$VERBOSE_ECHO "\$ $cmd"
	output=`eval time -p "$cmd" 2>&1 | grep -v Using`

	# stop the limit timer.  when we get here, see if there is a
	# sleep process still running.  if any found, the sleep
	# processes are killed and waited for so that we successfully
	# avoid the parent shell reporting a "Terminated" process kill
	# message.

	for pid in `ps xj | grep $spid | grep sleep | grep -v grep | awk '{print $2}'` ; do
	    # must kill sleep children first or they can continue running orphaned
	    kill $pid >/dev/null 2>&1
	    wait $pid >/dev/null 2>&1
	done
	# must wait in order to suppress kill messages
	kill -9 $spid >/dev/null 2>&1
	wait $spid >/dev/null 2>&1

	$VERBOSE_ECHO "$output"
	real_nmg="`echo \"$output\" | tail -n 4 | grep real | awk '{print $2}'`"

	# verify NMG
	found=`$SGED -c "$work" search . -name \"${obj}.nmg\" 2>&1 | grep -v Using`
	if test "x$found" = "x${object}.nmg" ; then
	    nmg=pass
	    nmg_count=`expr $nmg_count + 1`
	fi
	if [ -e "./${obj}.nmg.extl" ] ; then
	    rm "./${obj}.nmg.extl"
	    nmg=extl
	fi

	# start the limit timer, same as above.
	{ sleep $MAXTIME && test "x`ps auxwww | grep "$work" | grep facetize | grep "${obj}.bot" | awk '{print $2}'`" != "x" && touch "./${obj}.bot.extl" && kill -9 `ps auxwww | grep "$work" | grep facetize | grep "${obj}.bot" | awk '{print $2}'` 2>&4 & } 4>&2 2>/dev/null
	spid=$!

	# convert BoT
	bot=fail
	cmd="$GED -c \"$work\" facetize \"${obj}.bot\" \"${obj}\""
	$VERBOSE_ECHO "\$ $cmd"
	output=`eval time -p "$cmd" 2>&1 | grep -v Using`

	# stop the limit timer, same as above.
	for pid in `ps xj | grep $spid | grep sleep | grep -v grep | awk '{print $2}'` ; do
	    # must kill sleep children first or they can continue running orphaned
	    kill $pid >/dev/null 2>&1
	    wait $pid >/dev/null 2>&1
	done
	# must wait in order to suppress kill messages
	kill -9 $spid >/dev/null 2>&1
	wait $spid >/dev/null 2>&1

	$VERBOSE_ECHO "$output"
	real_bot="`echo \"$output\" | tail -n 4 | grep real | awk '{print $2}'`"

	# verify BoT
	found=`$SGED -c "$work" search . -name \"${obj}.bot\" 2>&1 | grep -v Using`
	if test "x$found" = "x${object}.bot" ; then
	    bot=pass
	    bot_count=`expr $bot_count + 1`
	fi
	if [ -e "./${obj}.bot.extl" ] ; then
	    rm "./${obj}.bot.extl"
	    bot=extl
	fi

	# start the limit timer, same as above.
	{ sleep $MAXTIME && test "x`ps auxwww | grep "$work" | grep brep | grep "${obj}.brep" | awk '{print $2}'`" != "x" && touch "./${obj}.brep.extl" && kill -9 `ps auxwww | grep "$work" | grep brep | grep "${obj}.brep" | awk '{print $2}'` 2>&4 & } 4>&2 2>/dev/null
	spid=$!

	# convert Brep
	brep=fail
	cmd="$GED -c \"$work\" brep \"${obj}\" \"${obj}.brep\""
	$VERBOSE_ECHO "\$ $cmd"
	output=`eval time -p "$cmd" 2>&1 | grep -v Using`

	# stop the limit timer, same as above.
	for pid in `ps xj | grep $spid | grep sleep | grep -v grep | awk '{print $2}'` ; do
	    # must kill sleep children first or they can continue running orphaned
	    kill $pid >/dev/null 2>&1
	    wait $pid >/dev/null 2>&1
	done
	# must wait in order to suppress kill messages
	kill -9 $spid >/dev/null 2>&1
	wait $spid >/dev/null 2>&1

	$VERBOSE_ECHO "$output"
	real_brep="`echo \"$output\" | tail -n 4 | grep real | awk '{print $2}'`"

	# verify Brep
	found=`$SGED -c "$work" search . -name \"${obj}.brep\" 2>&1 | grep -v Using`
	if test "x$found" = "x${object}.brep" ; then
	    brep=pass
	    brep_count=`expr $brep_count + 1`
	else
	    found2=`$SGED -c "$work" search . -name \"${obj}.${obj}.brep\" 2>&1 | grep -v Using`
	    if test "x$found2" = "x${object}.${object}.brep" ; then
		brep=pass
		brep_count=`expr $brep_count + 1`
	    fi
	fi
	if [ -e "./${obj}.brep.extl" ] ; then
	    rm "./${obj}.brep.extl"
	    brep=extl
	fi

	# print result for this object
	status=fail
	if test "x$nmg" = "xpass" && test "x$bot" = "xpass" && test "x$brep" = "xpass" ; then
	    status=ok
	fi

	count=`expr $count + 1`

	SECONDS=`echo $real_nmg $real_bot $real_brep | awk '{print ($1+$2+$3)}'`
	$ECHO "%-4s\tnmg: %s %ss\tbot: %s %ss\tbrep: %s %ss %6.2fs %*s%.0f %s:%s" \
	       \"$status\" \"$nmg\" \"$real_nmg\" \"$bot\" \"$real_bot\" \"$brep\" \"$real_brep\" \"$SECONDS\" \
	       \"`expr 7 - $count : '.*'`\" \"#\" $count \"$file\" \"$object\"

    done

    # restore stdin
    exec 0<&3

    files=`expr $files + 1`

    # remove the file if so directed
    if test "x$KEEP" = "x0" ; then
	rm -f "$work"
    fi

    shift
done
end=`elapsed` # stop elapsed runtime timer
$ECHO "%s" "-=-"

# calculate summary statistics
elp=`echo $begin $end | awk '{print $2-$1}'`
nmg_fail=`echo $nmg_count $count | awk '{print $2-$1}'`
bot_fail=`echo $bot_count $count | awk '{print $2-$1}'`
brep_fail=`echo $brep_count $count | awk '{print $2-$1}'`
if test $count -eq 0 ; then
    nmg_percent=0
    bot_percent=0
    brep_percent=0
    rate=0
    avg=0
else
    nmg_percent=`echo $nmg_count $count | awk '{print ($1/$2)*100.0}'`
    bot_percent=`echo $bot_count $count | awk '{print ($1/$2)*100.0}'`
    brep_percent=`echo $brep_count $count | awk '{print ($1/$2)*100.0}'`
    rate=`echo $nmg_count $bot_count $brep_count $count | awk '{print ($1+$2+$3)/($4+$4+$4)*100.0}'`
    avg=`echo $elp $count | awk '{print $1/$2}'`
fi

# print summary
$ECHO
$ECHO "... Done."
$ECHO
$ECHO "Summary:"
$ECHO
$ECHO "   Files:  %.0f" $files
$ECHO " Objects:  %.0f" $count
$ECHO "Failures:  %.0f NMG, %.0f BoT, %.0f Brep" $nmg_fail $bot_fail $brep_fail
$ECHO "NMG conversion:  %.1f%%  (%.0f of %.0f objects)" $nmg_percent $nmg_count $count
$ECHO "BoT conversion:  %.1f%%  (%.0f of %.0f objects)" $bot_percent $bot_count $count
$ECHO "Brep conversion:  %.1f%%  (%.0f of %.0f objects)" $brep_percent $brep_count $count
$ECHO "  Success rate:  %.1f%%" $rate
$ECHO
$ECHO "Elapsed:  %.0f seconds" $elp
$ECHO "Average:  %.1f seconds per object" $avg
$ECHO
$ECHO "Finished running $THIS on `date`"
$ECHO "Output was saved to $LOGFILE from `pwd`"
$ECHO "Conversion testing complete."


# Local Variables:
# tab-width: 8
# mode: sh
# sh-indentation: 4
# sh-basic-offset: 4
# indent-tabs-mode: t
# End:
# ex: shiftwidth=4 tabstop=8
