#!/usr/bin/bash

# Copyright (c) 2006-2015, Universities Space Research Association (USRA).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * 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.
#     * Neither the name of the Universities Space Research Association nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY USRA ``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 USRA 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.

#
# There are 3 primary use cases for this script:
# 1. User wants to run the Universal Exec from a local Viewer.
# 2. User wants to run the UE, without a local Viewer
#    (but possibly with a remote one).
# 3. Viewer is running the UE on behalf of a user.
#
# Case 1 does error checking on the supplied arguments and passes them off
# to the Viewer.
# Case 2 does error checking on the arguments and runs the UE directly.
# Case 3 presumes the arguments are correct and runs the UE directly.
#

# An optional environment variable PLEXIL_EXECUTABLE names an alternate executable.

if [ -z "$PLEXIL_HOME" ]
then
    echo Error: Please set environment variable PLEXIL_HOME
    echo by loading /etc/profile.d/plexil.sh.
    echo Exiting.
    exit 1
fi

validate_file()
{
    if [ -z $1 ]
    then
        echo "Error: missing filename for $2 option"
        usage
        exit 2
    elif [ ! -e $1 ]
    then
        echo "Error: $1 does not exist"
        exit 1
    elif [ -d $1 ]
    then
        echo "Error: $1 is a directory"
        exit 1
    elif [ ! -r $1 ]
    then
        echo "Error: $1 cannot be read"
        exit 1
    fi
}

warn()
{
   if [ -z "$quiet" ]
   then
       echo "$1"
   fi
}

checker()
{
    if [ ! -x $PLEXIL_HOME/scripts/checkPlexil ]
    then 
	    echo Error: $PLEXIL_HOME/scripts/checkPlexil not found.	
	    exit 1
    fi
    if [ ! -r $plan_nm ]
    then
        echo Error: File $plan_nm does not exist, or is not readable.
        exit 1
    fi
    echo "Checking plan..."
    $PLEXIL_HOME/scripts/checkPlexil $plan_nm
    if [ $? -gt 0 ]
    then
	    echo Error: $plan_nm contains type errors. See the checker output for details.
        exit 1
	fi
}

summarize()
{
    echo Running executive from $PLEXIL_HOME
    if [ -n "$plan_nm" ]
    then
        echo "  Plan:           $plan_nm"
    else
        echo "  No plan"
    fi
    echo "  Configuration:  $config_nm"
    if [ -n "$library_dirs" ]
    then
        echo "  Library path:   $library_dirs"
    fi
    if [ -n "$libraries" ]
    then
        echo "  Libraries:      $libraries"
    fi
    if [ -n "$host_in" ]
    then
        echo "  Viewer host:    $host_in"
    fi
    if [ -n "$port_in" ]
    then
        echo "  Viewer port:    $port_in"
    fi
    echo
}

usage_text="Usage:\n\tplexilexec [-p <plan>] [-c <interface config>] [-d <debug file>] [-L <library dir>]* [-l <library>]*\
 [-v [-h <hostname>] [-n <portnumber>] [-b]] [-a] [-ch] [-q] \n"

usage()
{
    echo -e $usage_text
    echo -e "For more options enter 'plexilexec -help'"
}

help_text=" Options:\n\
\n\
 -help \n\
\t	displays this message\n\
 -p | -plan <plan> \n\
\t	The plan to run \n\
 -c | -config <config> \n\
\t 	Interface configuration file (default interface-config.xml) \n\
 -d | -debug <file name> \n\
\t 	Debug configuration file (default is Debug.cfg) \n\
 -L | -libraryPath <directory name> \n\
\t 	A directory to search for plan library files (option may be repeated) \n\
 -l | -library <file name> \n\
\t 	A plan library to load; may be node name or filename (option may be repeated) \n\
 -q | -quiet \n\
\t 	Minimize output \n\
 -v | -viewer \n\
\t 	Starts the Plexil Viewer \n\
 -b | -blocking \n\
\t 	Enables breakpoints in Viewer \n\
 -h | -hostname <name> \n\
\t 	Name of remote host where Viewer is running\n\
 -n | -port <number> \n\
\t 	Port number to use for Viewer \n\
 -ch | -check \n\
\t 	Runs the PLEXIL static checker prior to executing plan \n\
 -a | -autorun \n\
\t  Runs the specified plan immediately at Viewer startup \n\
 --for-viewer \n\
\t  Runs the exec with the supplied options, without checking them, and without starting the Viewer. \n\
\t  Intended for use by the Plexil Viewer or other scripts."

if [ $PLEXIL_EXECUTABLE ]
then
    prog=$PLEXIL_EXECUTABLE
elif [ -f /usr/bin/plexil-universalExec ] && [ -x /usr/bin/plexil-universalExec ]
then
    prog=/usr/bin/plexil-universalExec
elif [ -f $PLEXIL_HOME/bin/universalExec ] && [ -x $PLEXIL_HOME/bin/universalExec ]
then
    prog=$PLEXIL_HOME/bin/universalExec
elif [ -f $PLEXIL_HOME/bin/universalExec/universalExec ] && [ -x $PLEXIL_HOME/bin/universalExec/universalExec ]
then
    prog=$PLEXIL_HOME/bin/universalExec/universalExec
else
    echo "Error: Plexil executable not found, aborting"
    exit 1
fi

$PLEXIL_HOME/scripts/plexil-check-prog $prog

current_config="interface-config.xml" #current directory config
default_config_path="${PLEXIL_HOME}/examples" #default config location
default_config_name="dummy-config.xml"
default_config="${default_config_path}/${default_config_name}" 

no_plan=0 #assume plan required
quiet="" #quiet default
plan_nm="" #plan name
plan_cmd="" #plan command
config_nm="" #interface config name
config_cmd="" #interface config command
libraries="" #library files
library_dirs="" #library files
debug_cmd="" #debug cmd
check="" #run static checker
auto_run_cmd="" # Viewer autorun option

if [ ! $1 ]
then
  usage
  exit 1
fi

while [ -n "$1" ]
do
    save=$1
    case $1 in
        
	    -a | -autorun )
            auto_run_cmd=$1;;

	    -b | -blocking )
            block=$1;;

	    -c | -config )
	        shift
	        config_nm=$1;;

	    -ch | -check )
            check=$1;;

	    -d | -debug )
            shift
	        validate_file "$1" $save
            debug_cmd="-d $1";;

        --for-viewer )
            automate=$1;;

	    -h | -hostname )
            shift
	        host_in="$1";;

	    -help )
            echo -e $usage_text
            echo -e $help_text
	        exit 0;;

	    -L | -libraryPath )
            shift
            if [ -z "$1" ]
            then
                echo "Error: missing directory name for $save option"
                usage
                exit 2
            elif [ ! -r "$1" ] || [ ! -d "$1" ]
            then
                echo "Error: $1 is not a readable directory."
                exit 1
            else
                library_dirs="$library_dirs$1 "
	            lib_dirs_cmd="$lib_dirs_cmd-L $1 "
            fi;;

	    -l | -library )
            shift
	        validate_file "$1" $save
            libraries="$libraries$1 "
	        lib_cmd="$lib_cmd-l $1 ";;

	    -n | -port )
            shift
	        port_in=$1;;	#viewer port

	    -p | -plan )
	        shift
            validate_file "$1" $save
	        plan_nm=$1	
	        plan_cmd="-p $plan_nm";;

        -q | -quiet )
            quiet=$1;;

	    -v | -viewer )
            viewer=$1;;	#viewer listener enabled	

	    * )
            echo 'Error: unrecognized option' "'$1'"
	        usage
	        exit 2;;  
    esac
    shift
done

if [ -n "$automate" ]
then
    # Just Do It - presume caller knows what it's doing.
    # We do not expect a local Viewer to use the -h option.
    if [ -n "$plan_nm" ] && [ -n "$check" ]
    then
        checker
    fi
	config_cmd="-c $config_nm"
    if [ -z "$quiet" ]
    then
        summarize
    fi
    if [ -n "$port_in" ]
    then
        port_cmd="-n $port_in"
    fi
    echo "RUN_UE_PID$$"

    #echo $prog $plan_cmd $config_cmd $lib_cmd $lib_dirs_cmd $debug_cmd $viewer $port_cmd $block $quiet
    exec $prog $plan_cmd $config_cmd $lib_cmd $lib_dirs_cmd $debug_cmd $viewer $port_cmd $block $quiet
fi

if [ -n "$block" ]
then
    if [ -z "$viewer" ] && [ -z "$host_in" ] && [ -z "$port_in" ]
    then
        warn "Warning: $block specified but Viewer not in use. Ignoring."
        block=""
    fi
fi

# Check the supplied config
if [ -z "$config_nm" ]
then
    warn "Warning: no configuration file specified."
elif [ ! -r $config_nm ]
then
    warn "Warning: configuration file $config_nm not found"
    config_nm=""
fi

# Use default config if needed
if [ -z "$config_nm" ]
then  
    if [ -r "$current_config" ]
    then   
        config_nm=$current_config
    elif [ ! -r "$default_config" ]
    then
         echo Error: No configuration file specified and defaults cannot be found.
         exit 1
    else
	    config_nm=$default_config
    fi
    warn "Using default configuration file $config_nm"
fi
config_cmd="-c $config_nm"

# Check Viewer options
if [ -z "$viewer" ]
then
    # No local Viewer
    if [ -z "$host_in" ]
    then
        # Check local port, if supplied
        if [ -n "$port_in" ]
        then
            if ( echo `$PLEXIL_HOME/scripts/list_ports_in_use` | grep -q -v "\<${port_in}\>" )
            then
                echo "Error: No Viewer is listening on local port $port_in. Exiting."
                exit 1
            fi
        fi
    elif [ -z "$port_in" ]
    then
        echo "Error: Viewer host $host_in specified, but no port was supplied. Exiting."
        exit 1
    fi
    # TODO: Bounds check port number
elif [ -n "$host_in" ]
then
    echo "Error: -h (remove Viewer host) and -v (local Viewer) may not be combined. Exiting."
    exit 1
else
    # Local Viewer requested
    if [ -z "$port_in" ]
    then
        # Find open local port
        port_in=`$PLEXIL_HOME/scripts/find_open_port`
        if [ -z "$port_in" ]
        then
            echo "Unable to find an open local port for Viewer communications."
            exit 1
        fi
    elif ( echo `$PLEXIL_HOME/scripts/list_ports_in_use` | grep -q "\<${port_in}\>" )
    then
        echo "Error: Port $port_in is in use. Exiting."
        exit 1
    fi
fi

if [ -n "$host_in" ]
then
    host_cmd="-hostname $host_in"
fi

if [ -n "$port_in" ]
then
    port_cmd="-n $port_in"
fi
     
# Run default debug
if [ -z "$debug_cmd" ] && [ -z "$quiet" ] && [ -r "Debug.cfg" ]
then
    debug_cmd="-d Debug.cfg"
fi

# Check plan for errors
if [ -z "$viewer" ] && [ -n "$check" ]
then
    checker
fi

#separate command-line from plexexec output
echo

# Print summary if desired
if [ -z "$quiet" ] && [ -z "$viewer" ]
then
    summarize
fi

if [ -n "$viewer" ]
then
    # Launch the viewer, which will in turn invoke this script again with the automate flag
    #echo "plexil -plexilexec $plan_cmd $config_cmd $lib_dirs_cmd $lib_cmd $debug_cmd $host_cmd $port_cmd $check $block $auto_run_cmd"
    exec plexil -plexilexec $plan_cmd $config_cmd $lib_dirs_cmd $lib_cmd $debug_cmd $host_cmd $port_cmd $check $block $auto_run_cmd
else
    # User doesn't want viewer
    #echo "$prog $plan_cmd $config_cmd $lib_dirs_cmd $lib_cmd $debug_cmd $block"
    exec $prog $plan_cmd $config_cmd $lib_dirs_cmd $lib_cmd $debug_cmd $block
fi
