#!/bin/bash

### Copyright notice at the bottom of this file ###

# $Id: st,v 3.12 2011/06/01 23:10:20 adamm Exp $
#
# $Source: /home/adamm/src/st/RCS/st,v $

export DATEPROG="edate"
export DFLT_TIME=170000
export PROG="$(basename $0)"

export Arg
export BaseDir="$HOME/data/status"
export FakeDate=""
export Fold=0
export Now
export Options="a:d:DfpPrVwy"
export Prev=0
export Yesterday=0

export fileSpec="+status.%Y%m%d.%H%M%S"
export timeSpec="+%a %d %b %Y %H:%M:%S %s"


main() {
    # First thing, grab the current time
    Now=$($DATEPROG "+%s")

    # GNU-style help
    if [ "x$1" == "x--help" ] || [ "x$1" == "x--man" ] ; then
	usage
	exit 0
    fi

    # Not really a man page, but more details than --help
    if [ "x$1" == "x--manpage" ] ; then
	manpage
	exit 0
    fi

    # See version 2.9 for an alternate way to handle -V; specifically,
    # to call showVars before processing other args, in case we ever
    # want to see the default values for things like $Fold.

    # Parse the command line (a la POSIX)
    while getopts "$Options" opt 2> /dev/null ; do
	case $opt in
	\?)	usage 1>&2
		exit 1
		;;

	a)	BaseDir=$OPTARG
		;;

	d)	FakeDate=$OPTARG
		;;

	D)	function=dateStrs
		;;

	f)	Fold=1
		;;

	p)	Prev=1
		;;

	P)	showPath
		exit 0
		;;

	r)	function=statusReport
		;;

	V)	showVarsVers
		exit 0
		;;

	w)	function=weeklyReport
		;;

	y)	Yesterday=1
		;;
	esac
    done

    # Sanity check
    if [ "$FakeDate" ] && [ $Yesterday -eq 1 ] ; then
	printf "%s: can\'t use -d and -y together\n\n" $PROG 1>&2
	usage
	exit 1
    fi

    # Get rid of the switches (and their arguments)
    shift $(($OPTIND - 1))

    # Many things are easier if we guarantee that $BASE and the
    # expected subdirectories exist, so test for them and create them
    # if they're not there
    mkdir -p $BaseDir

    # Decide what to do based on how much is left on the command line
    # and whether we defined $function

    # No $function means start a new file -- but how?
    if ! [ "$function" ] ; then
	# Empty command line means start an editor
	if [ $# -eq 0 ] ; then
	    function=editFile
	else
	    # Words on the command line get stuffed into a new file
	    # (and don't bother starting an editor)
	    function=writeFile
	fi
    fi

    # If we haven't exited yet, call the appropriate function
    $function "$@"

    exit $?
} # main


checkArg() {
    # Did we even get a $1?
    if [ ! $1 ] ; then
	return
    fi

    # Always look for --help
    if [ X$1 == X--help ] ; then
	sth
	exit 0
    fi

    # For now, check $1 only if we're called as "st"
    if [ $PROG != "st" ] ; then
	return
    fi

    # -y means yesterday
    if [ X$1 == X-y ] ; then
	set $(dateops yesterday)${DFLT_TIME}
    fi

    # Get the number of digits
    n=$(expr $1 : '2[0-9]*')

    # Figure out what to do based on the number of digits
    case $n in
     8)	    Arg=${1}${DFLT_TIME}    ;;
    10)	    Arg=${1}0000	    ;;
    12)	    Arg=${1}00		    ;;
    14)	    Arg=$1		    ;;
     *)	    
	    # Fewer than 8, more than 14, or an odd number > 8 are all errors
	    echo "Usage: $PROG [ YYYYMMDD [ hh [ mm [ ss ] ] ]" 1>&2
	    exit 1
	    ;;
    esac
} # checkArg


#
# Usage: dateStrs
#
dateStrs() {
    # Figure out what to use for a date/time
    if [ $Yesterday -eq 1 ] ; then
	dsTime=$(dateops maketime "$(dateops yesterday)${DFLT_TIME}")
    elif [ "$FakeDate" ] ; then
	dsTime=$(dateops maketime $FakeDate)
    else
	dsTime=$Now
    fi

    $DATEPROG -t $dsTime "$fileSpec"
    dsStr=$($DATEPROG -t $dsTime "$timeSpec")
    echo "$dsStr"
    echo "$dsStr" | sed 's/./-/g'
} # dateStrs


#
# Usage: editFile
#
editFile() {
    # Create a temporary file and fill it with the template
    efTmpFile=$(mkstemp st.editFile.XXXXXX)
    fillFile > $efTmpFile
    efSumBefore=$(cksum $efTmpFile)

    ${EDITOR:-vi} $efTmpFile

    # Abandoned/aborted edit?
    efSumAfter=$(cksum $efTmpFile)
    if [ "$efSumBefore" == "$efSumAfter" ] ; then
	rm -f $efTmpFile
	printf "%s: edit appears to have been aborted\n" $PROG
	return $?
    fi

    # Create the real file

    # Decide what to use for a date/time
    if [ $Yesterday -eq 1 ] ; then
	efTime=$(dateops maketime "$(dateops yesterday)${DFLT_TIME}")
    elif [ "$FakeDate" ] ; then
	efTime=$(dateops maketime $FakeDate)
    else
	efTime=$Now
    fi

    efFile=$(makeFileName $efTime)

    # Add the header
    emitFileHeader $efTime > $efFile

    # Copy the temp file
    cat $efTmpFile >> $efFile

    ### I used to check for the user leaving the template in the
    ### file, but I don't think it's worth the effort; I can always
    ### put it back if necessary.

    # Make sure there's a blank line at the end
    if [ $(sed -n '$p' $efTmpFile | grep -c .) -gt 0 ] ; then
	echo "" >> $efFile
    fi
    ret=$?

    rm -f $efTmpFile
    printf "wrote %s\n" $(basename $efFile)
    return $ret
} # editFile


#
# Usage: emitFileHeader epochseconds
#
emitFileHeader() {
    str="$($DATEPROG -t $1 "$timeSpec")"
    echo "$str"
    echo "$str" | sed 's/./-/g'
} # emitFileHeader


#
# Usage: fillFile
#
fillFile() {
    echo "    ."
} # fillFile


#
# Usage: getNextSunday YYYYMMDD
#
getNextSunday() {
    gnsDate=$1
    gnsEpoch=$(dateops maketime $gnsDate)
    gnsDay=$($DATEPROG -t $gnsEpoch "+%a")

    while [ $gnsDay != "Sun" ] ; do
	gnsDate=$(dateops tomorrow $gnsDate)
	gnsEpoch=$(dateops maketime $gnsDate)
	gnsDay=$($DATEPROG -t $gnsEpoch "+%a")
    done

    echo $gnsDate
} # getNextSunday


#
# Usage: getPrevMonday YYYYMMDD
#
getPrevMonday() {
    gpmEpoch=$(dateops maketime $1)
    gpmDay=$($DATEPROG -t $gpmEpoch "+%a")

    while true ; do
	gpmDate=$($DATEPROG -t $gpmEpoch "+%Y%m%d")
	if [ "$gpmDay" == "Mon" ] ; then
	    echo $gpmDate
	    return
	else
	    gpmDate=$(dateops yesterday $gpmDate)
	    gpmEpoch=$(dateops maketime $gpmDate)
	    gpmDay=$($DATEPROG -t $gpmEpoch "+%a")
	fi
    done
} # getPrevMonday


#
# Usage: makeFileName epochseconds
#
makeFileName() {
    printf "%s/%s" $BaseDir $($DATEPROG -t $1 "$fileSpec")
} # makeFileName


#
# Usage: manpage
#
manpage() {
    printf "Options:\n"
    printf "    -a dir\n"
    printf "        alternate directory to store status entries\n\n"
    printf "    -d date1 | date1 [ date2 ]\n"
    printf "        YYYYMMDD [ HH [ MM [ SS ] ] ]\n\n"
    printf "    -f\n"
    printf "        fold -- show all entries for a given day under"
    printf " the earliest\n"
    printf "        timestamp heading\n\n"
    printf "    -p\n"
    printf "        previous week\n\n"
    printf "    -y\n"
    printf "        yesterday at %s\n\n" \
	$(echo $DFLT_TIME | sed 's/\(..\)\(..\)\(..\)/\1:\2:\3/')
} # manpage


#
# Usage: showPath
#
showPath() {
    echo "$BaseDir"
} # showPath


#
# Usage: showVarsVers
#
showVarsVers() {
    printf "BaseDir    = %s\n" "$BaseDir"
    printf "DFLT_TIME  = %s\n" "$DFLT_TIME"
    printf "fileSpec   = \"%s\"\n" "$fileSpec"
    printf "timeSpec   = \"%s\"\n" "$timeSpec"

    echo '$Id: st,v 3.12 2011/06/01 23:10:20 adamm Exp $' |
    awk '{
	sub(/,v/, "", $2)
	printf("\n%s v%s, %s %s UTC\n", $2, $3, $4, $5)
    }'
} # showVarsVers


#
# Usage: statusReport [ begin [ end ] ]
#
# begin, end are expected to be YYYYMMDD
#
statusReport() {
    srToday=$($DATEPROG "+%Y%m%d")

    # Set the begin and end dates
    case $# in
    0)	# No dates; yesterday if we got -y, else today
	if [ $Yesterday -eq 1 ] ; then
	    srBegin=$(dateops yesterday)
	else
	    srBegin=$($DATEPROG "+%Y%m%d")
	fi
	srEnd=$srBegin
	;;

    1)	# One date givea a report for a single day
	srBegin=$1
	srEnd=$1
	;;

    2)	# Two dates => begin, end
	srBegin=$1
	srEnd=$2
	;;
    esac

    cd $BaseDir
    srTmpFile=$(mkstemp st.statusReport.XXXXXX)
    ls status.????????.?????? > $srTmpFile

    while read srFile ; do
	srFileDate=$(echo $srFile | sed 's/status.\(........\)......./\1/')
	if [ $srFileDate -ge $srBegin ] && [ $srFileDate -le $srEnd ] ; then
	    if [ $Fold -ne 0 ] && [ $srFileDate == "$srHoldDate" ] ; then
		sed 1,2d $srFile
	    else
		cat $srFile
	    fi

	    srHoldDate=$srFileDate
	fi
    done < $srTmpFile

    rm -f $srTmpFile
} # statusReport


#
# Usage: usage
#
usage() {
    printf "Usage:\n"
    printf "    st [ -a dir ] [ -y | -d date1 ]\n"
    printf "        interactive entry\n\n"
    printf "    st [ -a dir ] [ -y | -d date1 ] -\n"
    printf "        write stdin to a new file\n\n"
    printf "    st [ -a dir ] [ -y | -d date1 ] word ...\n"
    printf "        write word ... to a new file\n\n"
    printf "    st --help | st --man\n"
    printf "        show this text\n\n"
    printf "    st --manpage\n"
    printf "        show additional information\n\n"
    printf "    st -D [ -a dir ] [ -y | -d date1 ]\n"
    printf "        generate date and file strings\n\n"
    printf "    st -r [ -a dir ] [ -f ] [ -y | date1 [ date2 ] ]\n"
    printf "        generate a report\n\n"
    printf "    st -w [ -a dir ] [ -f ] [ -p ]\n"
    printf "        generate a weekly report\n\n"
    printf "    st -P\n"
    printf "        show the absolute path to the data directory\n\n"
    printf "    st -V\n"
    printf "        show internal variables and version info\n"
    printf "\n"
    printf "Use \"st --manpage\" for more details.\n"
} # usage


#
# Usage: weeklyReport
#
# default is the most recent Monday through today; -p means the previous
# full week (Mon - Sun)
#
weeklyReport() {
    # Get today as day-of-week and YYYYMMDD; we'll need them later
    wrDay="$($DATEPROG "+%a")"
    wrYMD=$($DATEPROG "+%Y%m%d")

    # Figure out which pair of dates to use
    if [ $Prev -eq 0 ] ; then
	# This week
	if [ "$wrDay" == "Mon" ] ; then
	    wrBegin=$wrYMD
	    wrEnd=$wrBegin
	else
	    wrBegin=$(getPrevMonday $wrYMD)
	    wrEnd=$wrYMD
	fi
    else
	# Last week
	if [ "$wrDay" == "Mon" ] ; then
	    wrBegin=$(getPrevMonday $(dateops yesterday))
	else
	    wrBegin=$(getPrevMonday $(dateops yesterday))
	    wrBegin=$(dateops yesterday $wrBegin)
	    wrBegin=$(getPrevMonday $wrBegin)
	fi

	wrEnd=$(getNextSunday $wrBegin)
    fi

    statusReport $wrBegin $wrEnd
} # weeklyReport


#
# Usage: writeFile word [ ... ]
#
# If "word" is "-", read from stdin
#
writeFile() {
    # Decide what to use for a date/time
    if [ $Yesterday -eq 1 ] ; then
	wfTime=$(dateops maketime "$(dateops yesterday)${DFLT_TIME}")
    elif [ "$FakeDate" ] ; then
	wfTime=$(dateops maketime $FakeDate)
    else
	wfTime=$Now
    fi

    wfFile=$(makeFileName $wfTime)

    # See if the file already exists
    if [ -f $wfFile ] ; then
	printf "%s: file exists: %s; aborting\n" $PROG $wfFile
	return 1
    fi

    emitFileHeader $wfTime > $wfFile
    if [ "x$1" == "x-" ] ; then
	cat - | sed 's/^/    /' >> $wfFile
    else
	echo "$@" | sed 's/^/    /' >> $wfFile
    fi
    echo "" >> $wfFile

    printf "wrote %s\n" $(basename $wfFile)
    return 0
} # writeFile


main "$@"


###
### To-Do:
###	- finish manpage
###
### Enhancements:
###	- implement alternate week begin/end days (with $WEEK)
###	    export WEEK="Mon Sun"
###	    printf "WEEK     = \"%s\"\n" "$WEEK"
###
###	- implement -s (with -f, shortens the headers to dates only)
###
###	- implement -h str
###	    uses "str" as date format string for all headers
###

###
### Copyright, 2009, Adam Moskowitz. All rights reserved.
###
### Any redistribution of this software must retain the above copyright
### notice, this list of conditions, and the following disclaimer.
###
### Without specific prior written permission from the copyright holder,
### you may not charge a fee for the redistribution of this software.
###
### All other redistribution and use is hereby permitted.
###
### This software is provided "as is" and without any express or implied
### warranties, including, without limitation, the implied warranties of
### merchantability and fitness for a particular purpose.
###
