#!/bin/bash

#########################################
# Name: qsee
#
# Summary: generate xFOCE quick look plots
#
# Description: 
# Uses xpres to generate CSV data files
# then uses qplot to plot them.
#
# The easiest way to use qsee is to create
# config files for qsee, xpres and qplot
#
# Then, invoke qsee like this
#
#    qsee -f my.conf [options]
#
# Override/add (file) options by passing them 
# on the qsee command line:
#
#    qsee -l 24 < my.conf
#
# Author: kheadley
#
# Copyright MBARI 2014
#
#########################################

#########################################
# qsee config
#########################################
#
# qsee config files consist of a bash
# case statement with cases "env" and "tasks"
#
# the env case may contain environment 
# option key=value pairs:
#	QS_ARCH_PATH   : qsee arch directory
#	QS_OPATH       : qsee output dir
#	QS_LOG_DIR     : qsee log directory
#   QS_CLEAR_LOGS  : set "N" to keep logs older than QS_LOG_TTL_SEC
#   QS_LOG_TTL_SEC : delete log files older than this (sec)
#	QP_OPATH       : qplot output dir
#	XP_CFG         : xpres data config
#	XP_JCFG        : xpres jitter config
#	XP_OPT_BTIME   : begin time option (e.g. "-B <time>")
#	XP_OPT_ETIME   : end time
#	VERBOSE        : verbose output
#
# the tasks case may contain task function
# calls:
# 
#

#########################################
# Script configuration defaults
# casual users should not need to change
# anything below this section
#########################################

#XFHOME=${XFHOME:-"/home/xfoce"}
#XFSCRIPT_DIR=${XFSCRIPT_DIR:-"${XFHOME}/src/scripts"}
#
#QS_LOG="qsee-$$.log"
##QS_OPATH="${QS_OPATH:-plot-data}"
#QS_ARCH_PATH=${QS_ARCH_PATH:-"${XFHOME}/arch"}
#QS_JIT_AWK=${QS_JIT_AWK:-"${XFSCRIPT_DIR}/eps2jit.awk"}
#QS_CFG="qsee.conf"
#QS_CLEAR_LOGS="Y"
#QS_LOG_TTL_SEC=900
#unset QS_LOG_DIR
#
#XP_CMD="xpres"
#XP_CFG="${XFHOME}/conf/qfoce/xp-qfoce.conf"
#XP_JCFG="${XFHOME}/conf/qfoce/xp-qfoce-jit.conf"
#XP_OPTS=""
#
#QP_CMD="qplot"
##QP_OPATH="plot-output"
#QP_ODEVICE="png"

# make task list
declare -a TASK_LIST
let "tasks=0"

QS_TNOW=`date +%s`
unset XP_OPT_BTIME
unset XP_OPT_ETIME
unset VERBOSE
unset TEST_MODE



#################################
# Function Definitions
#################################
#################################
# name: sigTrap
# description: signal trap callback
# will interrupt 
# args: none
#################################
sigTrap(){
    exit 0;
}

#################################
# name: printUsage
# description: print use message
# args: none
#################################
printUsage(){
    echo
    echo "# batch process quick look plots"
    echo ""
    echo "usage: `basename $0` [options] plots..."
    echo "-f <cfg>   : config file       [$QS_XFG]"
    echo "-G <dir>   : log file dir      [$QS_LOG_DIR]"
	echo "-a <arch>  : arch path         [$QS_ARCH_PATH]"
    echo "-o <dir>   : output directory  [$QS_OPATH] "
    echo "-q <dir>   : qplot output path [$QP_OPATH] "
    echo "-p <file>  : plot config"
    echo "-x <file>  : xpres config      [$XPRES_CFG]"
    echo "-B <ets>   : begin time (epoch sec)"
    echo "-E <ets>   : end time"
    echo "-b <iso>   : begin time (iso yyyy/mm/ddThh:mm:ss)"
    echo "-e <iso>   : end time (iso)"
    echo "-l <n>     : last n hours"
    echo "-m <n>     : last n minutes"
    echo "-t         : test mode (show,don't execute tasks"
    echo "-T <task>  : specify task (execute in order specified)"
    echo "             <type>,opts..."
	echo "             xdata,src,str[,n=mod,x=xpconf]"
	echo "             jdata,src,str,alias,[x=xpconf]"
	echo "             eval,<code>"
	echo "             plot,qpconf"
	echo "             cplot,ofile,file,file..."
	
    echo "-V         : verbose output    [$VERBOSE]"
    echo "-h         : help "
    echo ""
    echo "The easiest way to use qsee is to create"
    echo "config files for qsee, xpres and qplot; each"
    echo "specifying the appropriate command line options"
    echo "(one option/value pair per line)."
    echo "The qsee config file reference the "
    echo "qplot configurations using the -p option."
    echo ""
    echo "Then, just invoke qsee like this"
    echo "    qsee < my.conf"
    echo ""
    echo " Override config file options by passing them "
    echo " on the command line:"
    echo "    qsee -l 24 < my.conf"
    echo

}

########################################
# name: vout
# description: print verbose message to stderr
# args:
#     msg: message
########################################
vout(){
if [ "${VERBOSE}" == "Y" ] || [ "${VERBOSE}" == "TRUE" ]
    then
	echo "$1" >&2
    fi
}

########################################
# name: imsg
# description: echos with indent
# args:
#     msg: message
#  indent: indent level
########################################
imsg(){
  OIFS=$IFS
  IFS=""
  msg_pad="                                        "
  let "msg_indent=${1}"
  shift
  all="${@}"
  printf "%s%s\n" ${msg_pad:0:${msg_indent}} ${all}
  IFS=$OIFS
}

########################################
# name: msg
# description: echos with default indent
# args:
#     msg: message
########################################
msg(){
 imsg $MSG_INDENT "${*}"
}

########################################
# name: exitError
# description: print use message to stderr
# args:
#     msg:        error message
#     returnCode: exit status to return
########################################
exitError(){
    echo >&2
    echo "$1" >&2
    echo >&2
    exit $2
}

# define tasks
########################################
# name: addPlotDataTask
# description: macro adds task to
# express data
# args:
#     msg: SRC, STR
########################################
addPlotDataTask(){
	SRC="${1}"
	STR="${2}"
    ALIAS="${3}"
	XP_OPTS="${XP_BASE_OPTS} -s ${SRC} -t ${STR}"
    XP_OFILE="${QS_OPATH}/${SRC}-${STR}.csv"
    # If alias provided, use that
    if [ "${ALIAS}" ]
    then
        XP_OFILE="${QS_OPATH}/${ALIAS}.csv"
    fi
	TASK_LIST[${tasks}]="${XP_CMD} ${XP_OPTS} < ${XP_CFG} > ${XP_OFILE} 2>> ${QS_LOG}"
	let "tasks++"
}

########################################
# name: addDelayDataTask
# description: macro adds task to
# express jitter data
# args:
#     msg: SRC, STR
########################################
addDelayDataTask(){
	SRC="${1}"
	STR="${2}"
	ALIAS="${3}"
	XP_OPTS="${XP_BASE_OPTS} -s ${SRC} -t ${STR}"
	XP_OFILE="${QS_OPATH}/${SRC}-${STR}-jit.csv"
    METRICS_OFILE="${QS_OPATH}/${SRC}-${STR}-jit.txt"
    # If alias provided, use that
    if [ "${ALIAS}" ]
    then
        XP_OFILE="${QS_OPATH}/${ALIAS}-jit.csv"
        METRICS_OFILE="${QS_OPATH}/${ALIAS}-jit.txt"
    fi
	AWK_OPTS="-v i=${ALIAS} -v o=${METRICS_OFILE}"
	TASK_LIST[${tasks}]="${XP_CMD} ${XP_OPTS} < ${XP_JCFG} | awk -f ${QS_JIT_AWK} ${AWK_OPTS} > ${XP_OFILE} 2>> ${QS_LOG}"
	let "tasks++"
}

########################################
# name: addPlotTask
# description: macro adds task to
# generate plot
# args:
#     msg: SRC, STR
########################################
addPlotTask(){
	QP_CONF="${1}"
#todo : remember this -j
	TASK_LIST[${tasks}]="${QP_CMD} ${QP_OPT_DEVICE} -a ${QS_OPATH} -O ${QP_OPATH} < ${QP_CONF} 2>> ${QS_LOG}"
	let "tasks++"
}

########################################
# name: addCplotTask
# description: macro adds task to
# generate plot
# args:
########################################
addCplotTask(){
	TASK_LIST[${tasks}]="combine_plots ${1} 2>> ${QS_LOG}"
	let "tasks++"
}

########################################
# name: addEvalTask
# description: macro adds task to
# generate plot
# args:
########################################
addEvalTask(){
	EV_TASK="${1}"
	TASK_LIST[${tasks}]="eval ${EV_TASK} 2>> ${QS_LOG}"
	let "tasks++"
}


########################################
# name: combine_plots
# description: combine a set of plots
# args: 
########################################
combine_plots(){
	CPLOT="${1}"
	echo "cplot[${CPLOT}]"
	unset plot_out
	unset filespec
	unset cplot_files
	unset cplot_xset

	# plot spec option: -C <output_file>,<file>[:<file>...]
	# Separate the output file from the input files
	CPIFSO=$IFS
	IFS="," 
	read plot_out filespec <<< "${CPLOT}"
	vout "plot_out[${plot_out}]"
	vout "filespec[$filespec]"

	# read the (colon delimited) files into an array
	# (may include wild cards)
	declare -a cplot_files
	IFS=":"
	read -a cplot_files <<< $filespec

	vout "cplotfiles[${#cplot_files[@]}]:[${cplot_files[@]}]"

	IFS=" "
	# expand file list, which may include wildcards
	for file in `echo ${cplot_files[@]}`
	do
		if [ -f "${file}" ]
		then
			vout "adding file[$file]"
			tmpset="${cplot_xset}$file "
			cplot_xset=${tmpset}
		else
			vout "couldn't find file[$file]"		
		fi
	done

	IFS=$CPIFSO
	vout "plotset[${cplot_xset}]"
	# input and output files are specified
	if [ "${plot_out}" ] && [ "${cplot_xset}" ]
	then

	# generate combined plot
	vout "combining plot set: [${QP_CMD} ${QP_OPT_DEVICE} -O ${QP_OPATH} -Co ${plot_out} ${cplot_xset[*]}]"
${QP_CMD} ${QP_OPT_DEVICE} -O ${QP_OPATH} -Co ${plot_out} ${cplot_xset[*]}
	else
	echo "output [${plot_out}] or input [${cplot_xset}] missing"
	fi
}

########################################
# name: parse_task
# description: parse task argument
# args:
#     task_spec
########################################
parse_task(){

	task_spec="${1}"
	
	#echo "parsing task spec[$task_spec]"

	IFSO=$IFS
	IFS=","
	declare -a task_opts
	read -a task_opts <<< "${task_spec}"

	echo "task spec[${task_spec}]"
	echo "task type[${task_opts[0]}]"
	
	case ${task_opts[0]} in
		xdata ) 
			echo "addPlotDataTask: src[${task_opts[1]}] str[${task_opts[2]}]"
			addPlotDataTask ${task_opts[1]} ${task_opts[2]}
		;;
		jdata )
			echo "addDelayDataTask: src[${task_opts[1]}] str[${task_opts[2]}] alias[${task_opts[3]}]"
			addPlotDataTask ${task_opts[1]} ${task_opts[2]} ${task_opts[3]}
		;;
		xtask ) 
			echo "addEvalTask: code[${task_opts[1]}]"
			addEvalTask ${task_opts[1]}
		;;
		plot ) 
			echo "addPlotTask: cfg[${task_opts[1]}]"
			addPlotTask ${task_opts[1]}
		;;
		cplot )
		;;
		* ) echo "unknown task[${task_opts[0]}]"
		;;
	esac
	
	IFS=$IFSO

}

########################################
# name: qs_clear_logs
# description: remove logs older than QS_LOG_TTL_SEC
# args:
#     args:       positional paramters
#     returnCode: none
########################################
qs_clear_logs(){
    if [ "${QS_CLEAR_LOGS}" == "Y" ]
    then

        if  [ "${QS_LOG_DIR}" ] && [ -d "${QS_LOG_DIR}" ]
        then
            for log in `ls $QS_LOG_DIR/qsee*log`
            do
                age_start=`stat -c %X $log`
                age_now=`date +%s`
                if [ "${age_start}" -gt 0 ]
                then
                    let "age=$age_now-$age_start"

                    if [ -f "${log}" ] && [ "${age}" -ge "${QS_LOG_TTL_SEC}" ]
                    then
                    echo "removing log[${log}]"
                    rm ${log}
                    fi
                fi

            done
        else
            for log in `ls qsee*log`
            do
                let "age=`date +%s`-`stat -c %X $log`"
                if [ -f "${log}" ] && [ "${age}" -ge "${QS_LOG_TTL_SEC}" ]
                then
                    echo "removing log[${log}]"
                    rm ${log}
                fi
            done
        fi
    fi
}

########################################
# name: processCmdLine
# description: do command line processsing
# args:
#     args:       positional paramters
#     returnCode: none
########################################
processCmdLine(){
	OPTIND=1
    vout "`basename $0` all args[$*]"
while getopts a:b:B:e:E:f:G:l:m:o:p:q:tT:x:hV Option
	do
		vout "`basename $0` Processing option - $Option[$OPTARG]"
		case $Option in
            a ) QS_ARCH_PATH=$OPTARG
            ;;
			b ) XP_OPT_BTIME="-b $OPTARG"
			;;
			B ) TEST=$OPTARG
				if [ "$TEST" -lt 0 ]
				then
					exitError "invalid start time [$TEST]"
				fi
				XP_OPT_BTIME="-B $OPTARG"
			;;
			e ) XP_OPT_ETIME="-e $OPTARG"
			;;
			E ) TEST=$OPTARG
				if [ "$TEST" -lt 0 ]
				then
					exitError "invalid end time [$TEST]"
				fi
				XP_OPT_ETIME="-E $OPTARG"
			;;
            f ) QS_CFG=$OPTARG
            ;;
            G ) QS_LOG_DIR=$OPTARG
            ;;
			l ) TEST=$OPTARG

                TMVAL=${TEST%%[Mm]*}
                THVAL=${TEST%%[Hh]*}
                if [ "${TMVAL}" != "${TEST}" ]
                then
                    if [ "$TMVAL" -lt 1 ]
                    then
                        exitError "invalid ltime [$TEST]"
                    fi
                    let "TB=$QS_TNOW-$TMVAL*60"
                else
                    if [ "$THVAL" -lt 1 ]
                    then
                        exitError "invalid ltime [$TEST]"
                    fi
                    let "TB=$QS_TNOW-$THVAL*3600"
                fi
#echo "TEST[$TEST] TMVAL[$TMVAL] THVAL[$THVAL] TB[$TB]"
#let "TB=$QS_TNOW-$TEST*3600"
				XP_OPT_BTIME="-B $TB"
				
			;;
			m ) TEST=$OPTARG
				if [ "$TEST" -lt 1 ]
				then
					exitError "invalid mtime [$TEST]"
				fi
				let "TB=$QS_TNOW-$TEST*60"
				XP_OPT_BTIME="-B $TB"
				
			;;
			o ) QSOUT_DIR=$OPTARG
			;;
			q ) QPOUT_DIR=$OPTARG
			;;
			t ) TEST_MODE="Y"
			;;
			T ) TASK_SPEC="${OPTARG}"
				parse_task "${TASK_SPEC}"
			;;
			x ) XPRES_CFG=$OPTARG
			;;
			V ) VERBOSE="Y"
			;;
			h)printUsage
			  exit 0
			;;
			*) exit 0 # getopts outputs error message
			;;
		esac
	done
}

########################################
# name: setConfig
# description: set configuration that
# depends on primary values, set unset defaults
# args: none
# returnCode: none
########################################
setConfig(){

    XFHOME=${XFHOME:-"/home/xfoce"}
    XFSCRIPT_DIR=${XFSCRIPT_DIR:-"${XFHOME}/src/scripts"}

    QS_LOG=${QS_LOG:-"qsee-$$.log"}
    QS_OPATH=${QS_OPATH:-"plot-data"}
    QS_ARCH_PATH=${QS_ARCH_PATH:-"${XFHOME}/arch"}
    QS_JIT_AWK=${QS_JIT_AWK:-"${XFSCRIPT_DIR}/eps2jit.awk"}
    #QS_CFG="qsee.conf"
    QS_CLEAR_LOGS=${QS_CLEAR_LOGS:-"Y"}
    QS_LOG_TTL_SEC=${QS_LOG_TTL_SEC:-900}
    #unset QS_LOG_DIR

    XP_CMD=${XP_CMD:-"xpres"}
    #XP_CFG="${XFHOME}/conf/qfoce/xp-qfoce.conf"
    #XP_JCFG="${XFHOME}/conf/qfoce/xp-qfoce-jit.conf"
    #XP_OPTS=""

    QP_CMD=${QP_CMD:-"qplot"}
    QP_OPATH=${QP_OPATH:-"plot-output"}
    QP_ODEVICE=${QP_ODEVICE:-"png"}


	XP_OPT_ARCH="-a ${QS_ARCH_PATH}"
	XP_BASE_OPTS="${XP_OPT_BTIME} ${XP_OPT_ETIME} ${XP_OPT_ARCH}"
    QP_OPT_DEVICE="-j ${QP_ODEVICE}"
    if [ "${QS_LOG_DIR}" ] && [ -d "${QS_LOG_DIR}" ]
    then
        local tmp="${QS_LOG_DIR}/${QS_LOG}"
        QS_LOG=${tmp}
    fi

}

########################################
# name: runTasks
# description: set configuration that
# depends on primary values
# args: TASK_LIST
# returnCode: none
########################################
runTasks(){
	vout "TASKS n=[${#TASK_LIST[@]}]:"
	NTASKS="${#TASK_LIST[@]}"
	let "i=0"
	let "j=$i+1"
	while [ "${i}" -lt "${#TASK_LIST[@]}" ]
	do
		if [ "${TEST_MODE}" == "Y" ]
		then
			echo "TASK[${j}/${NTASKS}]:[${TASK_LIST[$i]}]"
			echo ""
		else
			vout "TASK[${j}/${NTASKS}]:[${TASK_LIST[$i]}]"
			eval "${TASK_LIST[$i]}" 2>> ${QS_LOG}
			echo ""
		fi
		let "i++"
		let "j++"
	done
}

########################################
# name: dumpConfig
# description: show configuration
# args: none
# returnCode: none
########################################
dumpConfig(){
    vout "####### `basename $0` pid $$ config:"
	vout "XP_OPT_BTIME  : ${XP_OPT_BTIME}"
	vout "XP_OPT_ETIME  : ${XP_OPT_ETIME}"
    vout "QS_ARCH       : ${QS_ARCH_PATH}"
	vout "XP_CONF       : ${XP_CFG}"
	vout "XP_JCONF      : ${XP_JCFG}"
	vout "QS_OPATH      : ${QS_OPATH}"
	vout "QP_OPATH      : ${QP_OPATH}"
    vout "QS_LOG        : ${QS_LOG}"
    vout "QS_CLEAR_LOGS : ${QS_CLEAR_LOGS}"
	vout "VERBOSE       : ${VERBOSE}"
	vout "ARGS          : ${*}"
}

##########################
# Script main entry point
##########################

# arbitrate command line and
# config file options
# [command line overrides file options]
# - save command line options
# - process command line opts (check for cfg)
# - if cfg specified, read it's environment
#   overrides (source <cfg> env)
# - process command line (overrides cfg options)

# save command line args
declare -a cline_args
let "i=1"
while [ "$i" -le "$#" ]
do
cline_args[$i]="${@:$i:1}"
let "i+=1"
done

# set default delimiter to 
# newline for parsing/setting
# positional parameters
# [must restore after processing]
IFSO=$IFS
IFS=`echo` 

# apply command line settings
# (override config file options)
# 'set -' signals end of options
#set - ${cline_args[*]}	
processCmdLine ${cline_args[*]}

# if config file specified
# read that first
# using env option to pull in only 
# env settings
if [ -f "${QS_CFG}" ]
then
source ${QS_CFG} env
fi

#set - ${cline_args[*]}	
processCmdLine ${cline_args[*]}

let "i=$OPTIND-1"
while [ "${i}" -gt 0  ]
do
	shift
	let "i-=1"
done

# restore default delimiter
IFS=$IFSO

# set environment that 
# derives from other settings
setConfig

# clear logs
# [if QS_CLEAR_LOGS="Y"]
qs_clear_logs

# initialize log
echo "=== qsee start: `date` ===" &> ${QS_LOG}

# get tasks defined in config file
if [ -f "${QS_CFG}" ]
then
	source ${QS_CFG} tasks
fi

# show configuration (if verbose)
dumpConfig

if [ ! -e "${QS_OPATH}" ]
then
	mkdir -p ${QS_OPATH}
elif [ ! -d "${QS_OPATH}" ]
then
	exitError "output path is not a directory [${QS_OPATH}]" -1
fi

# execute tasks
runTasks

echo "=== qsee done: `date` ===" &>> ${QS_LOG}

exit 0
