#!/bin/bash
#
# This script creates a 3D visualisation of the calculation results of INVERT.
#
# The required AWK scripts "obsrange.awk", "splitcodes.awk", "plt2dat.awk" and
# "prof2map.awk" are expected to be found in the actual working directory or
# in the directory defined in the environment variable INVERTDIR. If INVERTDIR
# is not set or empty it defaults to "./invert".
#
# Dependencies on external programs:
#  bash
#  basename
#  cat
#  gawk
#  gnuplot (>= 4.0)
#  sed
#
# (C) 2008 Peter L. Smilde
#

if [ -z "${INVERTDIR}" ] ; then
		INVERTDIR="./invert"
fi

#==============================================================================

function usage()
{
		echo "Usage: `basename $0` [-o] [-m] [-r] [-g gnuplot_gridding_commands]"
    echo "       [-d gnuplot_display_commands] [-c colorbar-palette 0|1 ]"
		echo "       [-s label_shuffle_seed] [-p] [-b] [-w] [-h]<def-file>"
}

function showhelp()
{
		usage
		echo
		echo " - With options -o, -m and -r plotting observations, model effect or"
		echo "     residuals can be selected (multiple selections are allowed)."
		echo
		echo " - The options -g and -p allow the user to enter gnuplot commands which are "
		echo "     inserted just before the splot commands (-g for gridding, e.g."
		echo "     -g \"set dgrid3d 81,81,\" or -g \"set dgrid3d ,,4\" and -d for display,"
		echo "     e.g. -d \"set view 75,80,,1\" or -p \"set view map\")."
		echo
    echo " - With option -c two different color palettes for observations and model "
		echo "     effects can be selected: 0 is the default palette, 1 is a palette "
		echo "    showing a linear gradient, if plotted in gray scale."
		echo
		echo " - With different values of the seed for option -s the location of density"
		echo "     labels will be shuffled. Useful in case they are not well identifiable."
		echo
		echo " - With option -b the interactive mouse- and key binding are shown."
		echo
		echo " - Option -p creates a printable postscript file (extension: .ps)."
		echo
x		echo " - With option -w the windows version of gnuplot (pgnuplot) will be called."
		echo
}

#==============================================================================

function isequal()
{
		[ $(echo "$1 $2" | (LANG=C;gawk '{ print $1==$2; }' -)) -eq 1 ]
}

#==============================================================================

declare -a TYPES
MINXMESH=40
MINYMESH=40
NORM=4
GRIDCMD=""
DISPCMD=""
SEED=0
PALINDEX=0
PRINT=0
SHOWBIND=0
ISWGNUPLOT=0
OUTDIR="./inverttmp"

#-------------------------
# Get commandline options
#-------------------------

while getopts "omrd:g:c:s:cpbwh" OPTIONS ; do
		case $OPTIONS in
				o ) TYPES[${#TYPES[*]}]=3;;
				m ) TYPES[${#TYPES[*]}]=2;;
				r ) TYPES[${#TYPES[*]}]=1;;
				g ) GRIDCMD=$OPTARG;;
				d ) DISPCMD=$OPTARG;;
				s ) SEED=$OPTARG;;
				c ) PALINDEX=$OPTARG;;
        p ) PRINT=1;;
				b ) SHOWBIND=1;;
				w ) ISWGNUPLOT=1;;
				h ) showhelp; exit 0;;
				\? ) usage; exit 1;;
				* ) usage; exit 0;;
		esac
 
done
shift $(($OPTIND-1))

if [ ${#TYPES[*]} -eq 0 ] ; then
		TYPES=(3 2 1)
fi

if [ ${PALINDEX} -eq 1 ] ; then
		PALETTE="set palette model RGB defined (0 0 0 1, 0.22 0.725 0 0.725, 0.46 1 0.3 0.3, 0.79 0.895 0.895 0, 0.91 0.765 1 0.765, 1 0.965 1 1)"
else
		PALETTE="set palette  model HSV defined (0 0.66 1 0.3, 1 1 1 1, 1 0 1 1, 2.5 0.66 0 1)"
fi

if [ ${ISWGNUPLOT} -eq 0 ] ; then
		GNUPLOT="gnuplot"
		X11TERM=$(${GNUPLOT} --help | gawk '/X11/ { print "yes" ; exit }')
else
		GNUPLOT="pgnuplot"
fi

GNUPLOTVERSION=($(${GNUPLOT} --version | gawk '{ if (match($2,/[^.]*/)) { major=substr($2,RSTART,RSTART+RLENGHTH) } if (match($2,/[^.]*$/)) { minor=substr($2,RSTART,RSTART+RLENGHTH) } print major,minor }'))


#------------------------
# Check script directory
#------------------------

SCRIPTDIR=""
for DIR in "." "$INVERTDIR" ; do
		if [ -d "$DIR" -a -f "$DIR/obsrange.awk" -a -f "$DIR/splitcodes.awk" -a -f "$DIR/plt2dat.awk" -a -f "$DIR/prof2map.awk" ] ; then
				SCRIPTDIR="$DIR"
				break;
		fi
done

if [ -z "${SCRIPTDIR}" ] ; then
		echo "ERROR: Required AWK scripts not found in \".\" or \"$INVERTDIR\" (\$INVERTDIR)."
		exit 1;
fi

#-------------------
# Check input files
#-------------------

if [ "$1" = "" ] ; then
		usage
		exit 0;
elif [ ! -f "$1" ] ; then
		echo "ERROR: Definitions file \"$1\" not found."
		exit 1;
fi

DEFFILE="$1"

MODELNAME=$(echo "${DEFFILE}" | sed -e 's/\.[^.]*$//')

TYPENAMES=("" "Residuals" "Model effect" "Observations")

#------------------
# Observation file
#------------------

OBSFILE=$(gawk '/^ Observations file/ {	obsfile = $4; next }; END { print obsfile }' ${DEFFILE})

if [ ! -f "${OBSFILE}" ] ; then
		echo "ERROR: Observations file \"${OBSFILE}\" not found."
		exit 1;
fi

if [ ! -d "$OUTDIR" ]  ; then
		mkdir "$OUTDIR" || OUTDIR="."
fi

GPFILE="$OUTDIR/invert.plt"

#-------------------------------------------------
# Get real and square range of observation points
#-------------------------------------------------

RANGE=($(LANG="C";gawk -f "$INVERTDIR/obsrange.awk" ${OBSFILE}))

SQRANGE=($(LANG="C";echo "${RANGE[0]} ${RANGE[1]} ${RANGE[2]} ${RANGE[3]}"|gawk '{ xm=($1+$2)/2; ym=($3+$4)/2; dx=($2-$1)/1.8; dy=($4-$3)/1.8; dxy=dx>dy?dx:dy; print xm-dxy,xm+dxy,ym-dxy,ym+dxy }' -))

OFFSET=($(LANG="C";echo "${SQRANGE[0]} ${SQRANGE[1]}"|gawk '{ print ($2-$1)*0.1 }' -))


#--------------------------------------
# Get data codes of observation points
#--------------------------------------

ICODE=0
while [ ${ICODE} -lt $((${#RANGE[*]} - 14)) ] ; do
		DATACODES[${ICODE}]=${RANGE[$((${ICODE}+14))]}
		ICODE=$((${ICODE}+1))
done

if [ ${#DATACODES[*]} -gt 1 ] ; then
		gawk -f "$INVERTDIR/splitcodes.awk" -v name="$OUTDIR/observations" ${OBSFILE}
fi


#-------------------
# Model effect file
#-------------------

MODFILE="${MODELNAME}.mod"
if [ ! -f "${MODFILE}" ] ; then
		echo "NOTE: Model file \"${MODFILE}\" not found."
		echo "      Append option \"o\" to \"Output of observation results\" to plot the model"
		echo "      effect."
		echo

		NOMODFILE=1

elif [ "${MODFILE}" -ot "${DEFFILE}" ] ; then
		echo "WARNING: Model file \"${MODFILE}\" older than \"${DEFFILE}\"."
		echo

fi

if [ -z "${NOMODFILE}" -a ${#DATACODES[*]} -gt 1 ] ; then
		gawk -f "$INVERTDIR/splitcodes.awk" -v name="$OUTDIR/modeleffect" "${MODFILE}"
fi

#----------------
# Residuals file
#----------------

RESFILE="${MODELNAME}.res"
if [ ! -f "${RESFILE}" ] ; then
		echo "NOTE: Residuals file \"${RESFILE}\" not found."
		echo "      Append option \"r\" to \"Output of observation results\" to plot residuals."
		echo

		NORESFILE=1

elif [ "${RESFILE}" -ot "${DEFFILE}" ] ; then
		echo "WARNING: Residuals file \"${RESFILE}\" older than \"${DEFFILE}\"."
		echo

fi

if [ -z "${NORESFILE}" -a  ${#DATACODES[*]} -gt 1 ] ; then
		gawk -f "$INVERTDIR/splitcodes.awk" -v name="$OUTDIR/residuals" "${RESFILE}"
fi

#-----------
# Plot file
#-----------

PLTFILE="${MODELNAME}.plt"
if [ ! -f "${PLTFILE}" ] ; then
		echo "NOTE: Plot file \"${PLTFILE}\" not found."
		echo "      Append option \"M\" or \"m\" to \"Output in separate file\" to plot the model."
		echo

		NOPLTFILE=1

else
		if [ "${PLTFILE}" -ot "${DEFFILE}" ] ; then
				echo "WARNING: Plot file \"${PLTFILE}\" older than \"${DEFFILE}\"."
				echo
		fi

#----------------
# Parse plt-file
#----------------

		CONFIG=($(LANG="C";gawk -f "$INVERTDIR/plt2dat.awk" -v outdir="${OUTDIR}" -v dim=3 -v xmin=${RANGE[0]} -v xmax=${RANGE[1]} -v ymin=${RANGE[2]} -v ymax=${RANGE[3]} -v seed=${SEED} "${PLTFILE}"))

		LABELINCOUNT=${CONFIG[0]}
		LABELOUTCOUNT=${CONFIG[1]}
		SPHEREINCOUNT=${CONFIG[2]}
		SPHEREOUTCOUNT=${CONFIG[3]}
		PLATEINCOUNT=${CONFIG[4]}
		PLATEOUTCOUNT=${CONFIG[5]}
		PROFILEINCOUNT=${CONFIG[6]}
		PROFILEOUTCOUNT=${CONFIG[7]}
fi


#----------------------
# Loop over data codes
#----------------------

for CODE in ${DATACODES[*]} ; do

		if [ ${#DATACODES[*]} -gt 1 ] ; then
				OBSFILE="${OUTDIR}/observations${CODE}.dat"
				MODFILE="${OUTDIR}/modeleffect${CODE}.dat"
				RESFILE="${OUTDIR}/residuals${CODE}.dat"

				if [ ! -f ${OBSFILE} ] ; then
						echo "ERROR: Incorrect data code splitting of observations."
						echo
						exit 1;
				fi

				if [ ! -f ${MODFILE} -o ${MODFILE} -ot ${OBSFILE} ] ; then
						echo "ERROR: Incorrect data code splitting of model effect."
						echo
						exit 1;
				fi

				if [ ! -f ${RESFILE} -o ${RESFILE} -ot ${OBSFILE} ] ; then
						echo "ERROR: Incorrect data code splitting of residuals."
						echo
						exit 1;
				fi
		fi


#-------------------------
# Loop over output types
#-------------------------

		for TYPE in ${TYPES[*]} ; do


#-------------------------------------------------------
# Create gridding script for scattered observation data
#-------------------------------------------------------

		# Observations
				if [ ${TYPE} -eq 3 ] ; then
						SCATFILE="${OBSFILE}"
						RANGE=($(LANG="C";gawk -f "$INVERTDIR/obsrange.awk" -v nxmin=${MINXMESH} -v nymin=${MINYMESH} ${SCATFILE}))
						CBRANGE=(${RANGE[4]} ${RANGE[5]})
						EXTENSION="obs"

		# Model effect
				elif [ ${TYPE} -eq 2 -a -z "${NOMODFILE}" ]; then
						SCATFILE="${MODFILE}"
						RANGE=($(LANG="C";gawk -f "$INVERTDIR/obsrange.awk" -v nxmin=${MINXMESH} -v nymin=${MINYMESH} ${SCATFILE}))
						CBRANGE=(${RANGE[4]} ${RANGE[5]})
						EXTENSION="mod"

		# Residuals
				elif [ ${TYPE} -eq 1 -a -z "${NORESFILE}" ] ; then
						SCATFILE="${RESFILE}"
						RANGE=($(LANG="C";gawk -f "$INVERTDIR/obsrange.awk" -v nxmin=${MINXMESH} -v nymin=${MINYMESH} ${SCATFILE}))
						CBRANGE=($(LANG="C";echo "${RANGE[4]} ${RANGE[5]}" | gawk '{ amin=($1<0)?-$1:$1; amax=($2<0)?-$2:$2; abs=(amin>amax)?amin:amax; print -abs,abs }' -))
						EXTENSION="res"
				else
						continue
				fi

				if isequal "${CBRANGE[0]}" "${CBRANGE[1]}" ; then
						CBRANGE=($(LANG="C";echo ${CBRANGE[@]} | gawk '{ print $1-1,$2+1 }' -))
				fi 

		# Profile at constant x
				if isequal "${RANGE[0]}" "${RANGE[1]}" ; then
						(LANG="C";gawk -f "$INVERTDIR/prof2map.awk" -v xoffset=${OFFSET} "${SCATFILE}") > "$OUTDIR/gridfile.dat"
						ESCATFILE="$OUTDIR/gridfile.dat"
						XMESH=${RANGE[13]}
						YMESH=2

		# Profile at constant y
				elif isequal "${RANGE[2]}" "${RANGE[3]}" ; then
						(LANG="C";gawk -f "$INVERTDIR/prof2map.awk" -v yoffset=${OFFSET}  "${SCATFILE}") > "$OUTDIR/gridfile.dat"
						ESCATFILE="$OUTDIR/gridfile.dat"
						XMESH=2
						YMESH=${RANGE[12]}

		# 3D Data
				else
						ESCATFILE="${SCATFILE}"
						NRANGE=(${RANGE[*]})
						XMESH=${RANGE[13]}
						YMESH=${RANGE[12]}
				fi

				cat <<EOF > ${GPFILE}
set term table
set output "$OUTDIR/plotmap.dat"
set dgrid3d ${XMESH},${YMESH},${NORM}
EOF


#------------------------------
# Insert command line settings
#------------------------------

				if [ -n "${GRIDCMD}" ] ; then
						cat <<EOF >> ${GPFILE}
${GRIDCMD}
EOF
				fi

				cat <<EOF >> ${GPFILE}
splot "${ESCATFILE}" using 2:3:4
EOF


#------------------
# Perform gridding
#------------------

				if [ $ISWGNUPLOT == 0 ] ; then
						${GNUPLOT} ${GPFILE}
						FAILED=$?
				else
						${GNUPLOT} ${GPFILE} 2>"$OUTDIR/stderr.txt"
						FAILED=$?

#       Ignore "Can't find the gnuplot window" warning of wgnuplot:
						if [ ${FAILED} -ne 0 ] ; then
								if [ -f "$OUTDIR/stderr.txt" ] && gawk "{ if (\$0 != \"Can't find the gnuplot window\") { exit 1 } }" "$OUTDIR/stderr.txt" ; then
										FAILED=0;
								fi

						fi
				fi

				if [ ${FAILED} -eq 1 ] ; then
						echo
						echo "ERROR: gridding failed."
						exit 1
				fi


#--------------------
# Create plot script 
#--------------------

#------------------
# General settings 
#------------------

				if [ ${#DATACODES[*]} -gt 1 ] ; then
						DCEXT="_dc${CODE}"
						DCTITLE=" with data code ${CODE}"
				fi

				if [ "${X11TERM}" = "yes" -a ${PRINT} -eq 0 ] ; then
						POINTSIZE="1"
						VIEWSCALE="1.2"
				else
						POINTSIZE="0.5"
						VIEWSCALE="1"
				fi

				cat <<EOF > ${GPFILE}
EOF

				if [ ${PRINT} -eq 1 ] ; then
						cat <<EOF >> ${GPFILE}
set term postscript landscape enhanced color solid 10
set output "${MODELNAME}${DCEXT}_${EXTENSION}.ps"

EOF
				fi

				cat <<EOF >> ${GPFILE}
set size square
set view ,,${VIEWSCALE},0.5
set offset 0,0.3

set xrange [${SQRANGE[0]}:${SQRANGE[1]}]
set yrange [${SQRANGE[2]}:${SQRANGE[3]}]

set ticslevel 0.05

set xlabel "X [km]"
set ylabel "Y [km]"
set zlabel "Z [km]"

set border 4095
set grid front xtics ytics ztics

set pm3d explicit

set colorbox user size 0.025,0.3 origin 0.85,0.1
EOF


#---------------------------------
# Create title and color palettes
#---------------------------------

# Observations
				if [ ${TYPE} -eq 3 ] ; then
						cat <<EOF >> ${GPFILE}
set cblabel "dg (obs) [mGal]
set cbrange [${CBRANGE[0]}:${CBRANGE[1]}]
${PALETTE}

set title "Model \"${MODELNAME}\": Observations${DCTITLE}"

EOF

# Model effect
				elif [ ${TYPE} -eq 2 ] ; then
						cat <<EOF >> ${GPFILE}
set cblabel "dg (calc) [mGal]
set cbrange [${CBRANGE[0]}:${CBRANGE[1]}]
${PALETTE}

set title "Model \"${MODELNAME}\": Model effect${DCTITLE}"

EOF

# Residuals
				elif [ ${TYPE} -eq 1 ] ; then
						cat <<EOF >> ${GPFILE}
set cblabel "dg (resid) [mGal]
set cbrange [${CBRANGE[0]}:${CBRANGE[1]}]
set palette model RGB defined (0 0 0 1, 1 1 1 1, 2 1 0 0)

set title "Model \"${MODELNAME}\": Residuals${DCTITLE}"

EOF

				fi


#------------------------
# Load labeling commands
#------------------------

				if [ ${LABELINCOUNT} -gt 0 -a -f "$OUTDIR/labels_in.dat" ] ; then
						cat <<EOF >> ${GPFILE}
load "$OUTDIR/labels_in.dat"
EOF
				fi
				if [ ${LABELOUTCOUNT} -gt 0 -a -f "$OUTDIR/labels_out.dat" ] ; then
						cat <<EOF >> ${GPFILE}
load "$OUTDIR/labels_out.dat"
EOF
				fi



#------------------------------
# Insert command line settings
#------------------------------

				if [ -n "${DISPCMD}" ] ; then
						cat <<EOF >> ${GPFILE}
${DISPCMD}
EOF
				fi


#------------------------------------
# Compose "surface" plotting command
#------------------------------------

				cat <<EOF >> ${GPFILE}

splot \\
EOF

#-----------------------
# Plot observation data
#-----------------------

				cat <<EOF >> ${GPFILE}
  "$OUTDIR/plotmap.dat" using 1:2:(0):3 with pm3d at b notitle, \\
EOF


#-------------
# Plot bodies
#-------------

				if [ ${SPHEREOUTCOUNT} -gt 0 -a -f "$OUTDIR/spheres_out.dat" ] ; then
						cat <<EOF >> ${GPFILE}
  "$OUTDIR/spheres_out.dat" using 1:2:3 with points pt 7 ps 2*${POINTSIZE} lt 1 notitle, \\
EOF
				fi
				if [ ${SPHEREINCOUNT} -gt 0 -a -f "$OUTDIR/spheres_in.dat" ] ; then
						cat <<EOF >> ${GPFILE}
  "$OUTDIR/spheres_in.dat" using 1:2:3 with points pt 7 ps ${POINTSIZE} lt 3 notitle, \\
EOF
				fi

				if [ ${PLATEOUTCOUNT} -gt 0 -a -f "$OUTDIR/plates_in.dat" ] ; then
						cat <<EOF >> ${GPFILE}
  "$OUTDIR/plates_out.dat" using 1:2:3 with lines lt 1 lw 3 notitle, \\
EOF
				fi
				if [ ${PLATEINCOUNT} -gt 0 -a -f "$OUTDIR/plates_in.dat" ] ; then
						cat <<EOF >> ${GPFILE}
  "$OUTDIR/plates_in.dat" using 1:2:3 with lines lt 3 lw 1 notitle, \\
EOF
				fi

				if [ ${PROFILEOUTCOUNT} -gt 0 -a -f "$OUTDIR/profiles_in.dat" ] ; then
						cat <<EOF >> ${GPFILE}
  "$OUTDIR/profiles_out.dat" using 1:2:3 with lines lt 1 lw 3 notitle, \\
EOF
				fi
				if [ ${PROFILEINCOUNT} -gt 0 -a -f "$OUTDIR/profiles_in.dat" ] ; then
						cat <<EOF >> ${GPFILE}
  "$OUTDIR/profiles_in.dat" using 1:2:3 with lines lt 3 lw 1 notitle, \\
EOF
				fi


#-------------------------
# Plot observation points
#-------------------------

				cat <<EOF >> ${GPFILE}
  "${SCATFILE}" using 2:3:6 with points pt 7 ps ${POINTSIZE} lt 2 notitle, \\
  "${SCATFILE}" using 2:3:6 with points pt 7 ps 0.5*${POINTSIZE} lt -1 notitle, \\
EOF


#---------------
# Create legend
#---------------

				if [ $((${SPHEREINCOUNT} + ${PLATEINCOUNT} + ${PROFILEINCOUNT})) -gt 0 ] ; then
						cat <<EOF >> ${GPFILE}
  1/0 with lines lt 3 lw 1 title "a-priori", \\
EOF
				fi

				if [ $((${SPHEREOUTCOUNT} + ${PLATEOUTCOUNT} + ${PROFILEOUTCOUNT})) -gt 0 ] ; then
						cat <<EOF >> ${GPFILE}
  1/0 with lines lt 1 lw 3 title "a-posteriori", \\
EOF
				fi

				cat <<EOF >> ${GPFILE}
  1/0 with points pt 7 ps ${POINTSIZE} lt 2 title "data points", \\
  1/0 notitle
EOF


#----------------------------------
# Finalize script and call gnuplot
#----------------------------------

#---------------------------------------
# Define shut down of gnuplot by hotkey
#---------------------------------------

# By gnuplot exit command
				if [ ${GNUPLOTVERSION[0]} -ge 4 -a ${GNUPLOTVERSION[1]} -ge 2 ] ; then
						cat <<EOF >> ${GPFILE}
bind x "exit gnuplot"
EOF

# By killing PPID from shell variable
				elif [ $($SHELL -c "echo $PPID") -eq $PPID ] 1>/dev/null 2>&1 ; then
						cat <<EOF >> ${GPFILE}
bind x "system \"kill \$PPID\" # (i.e. exit gnuplot)"
EOF

# By killing PPID from ps command
				elif [ $(ps -p $$ -o %P --no-header) -eq $PPID ] 1>/dev/null 2>&1 ; then
						cat <<EOF >> ${GPFILE}
bind x "system \"kill \`ps -p \$\$ -o %P --no-header\`\" # (i.e. exit gnuplot)"
EOF
				fi


				if [ ${SHOWBIND} -eq 1 ] ; then
						cat <<EOF >> ${GPFILE}
bind
EOF
				fi
				
				if [ ${PRINT} -eq 0 ] ; then
						cat <<EOF >> ${GPFILE}

print "This gnuplot instance controls the plot of:
print ""
print "     ${TYPENAMES[${TYPE}]} of model \"${MODELNAME}\""
print ""
print "Press \"x\" in plot window or \"q\" here to close both windows."
print ""
EOF
				fi


#--------------
# Call gnuplot
#--------------

				if [ ${ISWGNUPLOT} -eq 0 ] ; then
						if [ ${PRINT} -eq 0 ] ; then
								cat <<EOF >> ${GPFILE}
pause -1
EOF

								if [ ${SHOWBIND} -eq 0 ] ; then
										xterm -iconic -e ${GNUPLOT} -persist ${GPFILE} &
								else
										xterm -e ${GNUPLOT} -persist ${GPFILE} &
										SHOWBIND=0
								fi

						else
								${GNUPLOT} ${GPFILE}

						fi

				else
						if [ ${PRINT} -eq 0 ] ; then
								${GNUPLOT} -persist ${GPFILE} &

						else
								${GNUPLOT} ${GPFILE} &

						fi
				fi

				read -t 1 ANS  # necessary to avoid premature shut down of gnuplot.

		done  # output types

done  # data codes