#! /bin/bash  -

# H.A.Trujillo,    11 Dec 2012
# Last Change:     10 Sep 2019

# 27mar14:  "histo.app" created using cupsfilter (native to OS)
#	      rather than enscript (manually installed).
# 10jul14:  smart choice of enscript vs cupsfilter. 
# 	    histo is now hard-linked to the script in histo.app
# 15jul14:  sanitized input
# 16dec14:  rounding of input data; -t and -r options
# 11may16:  -f option (full-scale)
# 19dec16:  -o option (output name)
# 07dec18:  enscript output now right-side up, rather than rotated 90°
#		(can't make CUPS output rotate correctly, however)
# 16dec18   defaults to full-scale
# 10sep19   auto-scales y axis

PATH=/usr/bin:/bin/:/opt/local/bin:/usr/local/bin:/usr/sbin:/sbin:
output=/tmp/histo	# output defaults to /tmp/histo.ps and /tmp/histo.pdf

FullScale=1	# default to full-scale  (comment out to default to zoom)

while [ $1 ] ; do
  case $1 in 
    -h)  cat <<- EOT

	histo:

	    Produces a histogram from data entered on stdin.

	    Output is left in /tmp/histo.pdf, unless the "-o" option is
	    used.  If enscript is installed, a printer-ready postscript 
	    file, /tmp/histo.ps, is also created.
	
	    usage:
	          pbpaste | histo
	          histo < datafile       (newline, comma, or tab delimited)
	       or
	          copy data into clipboard; click histo.app
	
	    options:
	          -f      full scale    (0 -> 100)		(default behavior)
	          -z      zoom on data  (ie, not full-scale)
	          -y      hard y-scale -- forces large values offscale
	          -r      round data to the integer		(default behavior)
	          -t      truncate data at the integer
	          -o xx	  save output in file xx.pdf		(default = /tmp/histo)
	          -s      produce screen output rather than a file
	          -h      show this help

	    caveats:
	        Bins are integers.
	        The x-scale is maximized at 100.

	EOT
	  exit
	  ;;
    -f)  FullScale=1
	  ;;
    -z)  FullScale=0
	  ;;
    -y)  Hard_Y=1
	  ;;
    -t)  RoundtheInput=0
	  ;;
    -r)  RoundtheInput=1
	  ;;
    -s)  ScreenOutput=1 
	  ;;
    -o)  output="$2"
	 shift
	  ;;
     *)  printf "\noops!  unknown option, $1 \n\n"  >&2
	 exit 1
	  ;;
  esac
  shift
done

declare -a HASH COUNT

# Tidy data and place the number of occurrences for each score in an
# array: COUNT(score) = occurrences of <score>.

#	Use of a hash-file is a total kludge: 
#	   Putting the tr into the HASH array definition works fine
#	   on unix-generated data, but bombs when data from Excel is
#	   pasted in.
hashfile=`mktemp /tmp/histo.XXXX`

tr \\r\\t, \\n | tr -C '[[:digit:]\n.]' " " > $hashfile

if [ ${RoundtheInput:-1} == 1 ]
then
  # Round the input to the nearest integer (default behavior).
  #   Note that Excel columns are already rounded, hence *.5
  #   is rounded down, not up.
  HASH=( `sed -e 's/ *$//' -e '/^$/d'  $hashfile \
	  | awk '{print int($0 + 0.49)}'\
	  | sort -n	\
	  | uniq -c`)
else
  # Truncate the input
  HASH=( `sed -e 's/\.[0-9]*//' -e 's/ *$//' -e '/^$/d'  $hashfile \
	  | sort -n	\
	  | uniq -c`)
fi
rm $hashfile


let i=0  tallest=0
while [ $i -lt ${#HASH[*]} ]; do
    bin=${HASH[$((i+1))]}
    COUNT[$bin]=${HASH[$i]}
    let i+=2
    if [ ${COUNT[$bin]} -gt $tallest ]; then
	tallest=${COUNT[$bin]}
    fi
done


# Prepare the graphic
if [ $FullScale == 1 ] 
then
    min=0
    max=100
else
    max=$((bin + 5))
    if [ $max -gt 100 ] ; then
	max=100
    fi

    min=$((HASH[1] - 5))
    if [ $min -le 0 ] ; then
	min=0
    fi
fi
unset HASH


bar='************************************************************************//*'

if [ $Hard_Y ]
 then
    y_mult=5			# how many dots per item in histogram
    y_axis="    :    :    :    :    |    :    :    :    :    |    :    :    :    :     "
 else
    y_mult=$(( 75/tallest ))
    if [ $y_mult -gt 5 ] ; then y_mult=5 ; fi
    axes=(" " \
	"    .    |    .    |    .    |    .    |    .    |    .    |    .    |    " \
	" : : : : x : : : : | : : : : x : : : : | : : : : x : : : : | : : : : x : :" \
	"  :  :  :  :  x  :  :  :  :  |  :  :  :  :  x  :  :  :  :  |  :  :  :  :  " \
	"   :   :   :   :   |   :   :   :   :   |   :   :   :   :   |   :   :   :  " \
	"    :    :    :    :    |    :    :    :    :    |    :    :    :    :    " \
    )
    y_axis="${axes[$y_mult]}"
    if [ $y_mult == 0 ] ; then y_mult=1 ; fi
fi


if [ ! $ScreenOutput ] ; then
    tmpfile=`mktemp /tmp/histo.XXXX`
    exec 1> $tmpfile
fi

# print y-axis
printf "     | %-75.$((tallest * y_mult ))s\\n" "$y_axis"

# print x-axis and bars
i=$min
while [ $i -le $max ]; do
    case $(( i%10 )) in
	0) label=${i}- 	;;
	5) label="-"  	;;
	*) label="."  	;;
    esac
	
    length=$(( COUNT[$i] * y_mult  ))

    printf "%5s| %-75.${length}s\\n" "$label" "$bar"
    let i++
done


# Create postscript output

if [ ! $ScreenOutput ] ; then	

    if which -s enscript
    then
	if [ $((max - min)) -gt 65 ] ; then 	# keep on single page
	    leading="-s-`dc -e \"2k $max $min- 67- 10.60/ p \"`"
	fi

	cd /tmp
	enscript -i4 $leading -p "$output" -B $tmpfile

	# Make output right-side-up Landscape
	#	David Fanning, http://www.idlcoyote.com/ps_tips/landscapeup.html
	#		for A4 paper:	180 rotate -595.28 -841.89 translate
	#		for 8.5 x 11:	180 rotate -612 -792 translate 
	sed -e '/^%%Orientation:/s/Portrait/Landscape/' \
	    -e '/^%%Page:/a\
180 rotate -610 -805 translate 
'		 < "$output"  > "${output}.ps"

	if `which -s ps2pdf`
	then
	    ps2pdf  "${output}.ps" # avoid the save-dialog when closing Preview window
	    sleep 1
	else
	    cupsfilter "${output}.ps" > "${output}.pdf"
	fi

    else
	if [ $((max - min)) -gt 60 ] ; then 	# keep on single page
	    leading="-o lpi=`dc -e \" 1k $max $min- 10/ p \"`" 
	fi
	# nb/ can get CUPS to rotate paper, but not image on paper
	cupsfilter -o nowrap $leading $tmpfile > "${output}.pdf"
    fi

    open  "${output}.pdf"
fi

rm -f $tmpfile 
exit

# vim: nowrap
