#!/bin/sh
#set -x   ## uncomment for a trace
# (tabs are 3-spaces in width)

# 2023-Oct-29
# This material is free and without warranty.  Use it in any way you think fits.

#+++++ START: USER CONFIGURATION AREA ++++++++++++++++++++++++++++++++++++++++++
#
# NOTEs:
# - to determine the CSV file's list of fields, run the script/command:
#   rcCSVtoVBO.sh -i <CSV pathname>
# - below, RC is an abbreviation for RaceCapture
#
# VBO_FIELDS_<fields suffix> specifications:
# - multiple "VBO_FIELDS" specifications are allowed as
#   "VBO_FIELDS_<fields suffix>" where:
#   + VBO_FIELDS_MIN defines the minimum collection of fields required
#   + different <fields suffix> values can be appended to "VBO_FIELDS_" to
#     create various VBO_FIELDS specifications - e.g., for different cars and/or
#     different RaceCapture CSV file headers (i.e., field configurations)
#   + the <fields suffix> must only have alphabetic, alphanumeric and/or
#     underscore (_) characters (others can cause script errors)
#
# update the following items as required ...
#
# to specify the VBO fields -- the format is:
# <RC field name>[,[<format spec>][,[<smoothing flag>][,[<VBO header name>][,[<VBO column name>][,[<comment>]]]]]]
# <ignored lines>
#
# Where:  (items in square brackets are optional)
# <RC field name> is the name of the field in the RaceCapture CSV/log file:
#                 e.g., for header field '"Utc"|"ms"|0|0|1', the name is Utc
#                 (the names are case-sensitive)
#
# <format spec> is a printf-type format specification that will be used to
#               format that field data when placed in the VBO file -- note that
#               leading/trailing whitespace is removed from formatted data
#               values so, to generate fixed-length values, zero-fill and/or
#               sign-fill, don't space-fill
#
# <smoothing flag> is 1 if simple exponential data-smoothing is to be performed
#                  on this field's data values
#                  - NEVER apply data-smoothing to the "time" field
#                  - data-smoothing GPS coordinates may change shown lap times
#
# <VBO header name> is the corresponding name for the field in the [header]
#                   section of the VBO file -- if this entry is empty, then the
#                   <RC field name> is used
#
# <VBO column name> is the corresponding name for the field in the [column names]
#                   section of the VBO file -- if this entry is empty, then the
#                   <VBO header name> entry is used unless it is also empty, in
#                   which case the <RC field name> is used ... NO SPACES are
#                   allowed in column names
#                  (don't forget the 2 adjacent commas if appending a comment)
#
# <comment> is an optional comment field placed at then end of a field spec ...
#           don't forget to include any empty fields using adjacent commas
#
# <ignored lines)
# - tab characters are changed to a single space character
# - lines where the first non-whitespace character is a hash (#) are
#   ignored/removed
# - empty/whitespace-only lines are ignored/removed
# - lines with an empty/whitespace-only <RC field name> are ignored/removed
#
# NOTEs:
# - whitespace at the start/end of lines & adjacent to commas is ignored/removed
# - use only alphabetic, alphanumeric and/or underscore (_) characters in
#   comments
# - don't forget adjacent commas for empty fields that have a subsequent field
#
# IMPORTANT: the following is the minimal/required VBO_FIELDS definition:
# - the Utc/UTC field (that creates the VBO time field) MUST BE THE FIRST FIELD
# - don't remove any of these fields
# - other than Utc/time field, fields may be re-ordered
# - any additional entries must be appended to this list
# (i.e., this must be the first part of any VBO_FIELDS_<fields suffix> entry)
VBO_FIELDS_MIN='
# the following list of fields is required, with Utc ALEAYS being the first:
# RC field-      format  smooth         VBO           VBO column
# name part       spec     it        header name         name        comment
#------------  ----------  ---  --------------------  ---------  ----------------
         Utc ,   %010.3f ,  0 ,                time ,          , in VBO it is HHMMSS.ms
#
      AccelX ,    %+5.2f ,  0 ,         lat accel g ,   latacc
      AccelY ,    %+5.2f ,  0 ,        long accel g ,  longacc
#
    Latitude ,  %+010.4f ,  0 ,            latitude ,      lat
   Longitude ,  %+011.4f ,  0 ,           longitude ,     long
       Speed ,    %06.2f ,  0 ,        velocity kmh , velocity
    Altitude ,   %+09.2f ,  0 ,              height
     GPSSats ,      %02d ,  0 ,          satellites ,     sats
#
         RPM ,      %05d ,  0 ,                 RPM
         TPS ,      %03d ,  0 ,         ECUthrottle
#
 SessionTime ,    %07.3f ,  0 ,             heading ,          , calc from GPS lat/long
# NOTEs:
# - SessionTime is a placeholder for the heading, which is calculated
#   (the computed heading values do not seem to work properly in Circuit Tools)
# - the RaceCapture config/placement can yield reversed AccelX/Y fields, so the
#   AccelX/Y can be reversed via options or the REVERSE_ACCEL_XY setting below
'
#
# my GTR VBO_FIELDS definition
VBO_FIELDS_GTR='
# RC field-      format  smooth         VBO           VBO column
# name part       spec     it        header name         name        comment
#------------  ----------  ---  --------------------  ---------  ----------------
         Utc ,   %010.3f ,  0 ,                time ,          , in VBO it is HHMMSS.ms
#
      AccelX ,    %+5.2f ,  0 ,         lat accel g ,   latacc
      AccelY ,    %+5.2f ,  0 ,        long accel g ,  longacc
#
    Latitude ,  %+010.4f ,  0 ,            latitude ,      lat
   Longitude ,  %+011.4f ,  0 ,           longitude ,     long
       Speed ,    %06.2f ,  0 ,        velocity kmh , velocity
    Altitude ,   %+09.2f ,  0 ,              height
     GPSSats ,      %02d ,  0 ,          satellites ,     sats
#
         RPM ,      %05d ,  0 ,                 RPM
         TPS ,      %03d ,  0 ,         ECUthrottle
#
 SessionTime ,    %07.3f ,  0 ,             heading ,          , calc from GPS lat/long
# --- these are my additional fields
      AccelZ ,   %+06.2f ,  0 ,
        Gsum ,   %+05.2f ,  0 ,
#
  WheelSpeed ,    %05.1f ,  0 ,
  EngineLoad ,    %05.1f ,  0 ,
  EngineTemp ,    %05.1f ,  0 ,
#
    PredTime ,    %08.4f ,  0 ,
#
    Interval ,        %d ,  0 ,          RCinterval
'
#
# my GTR VBO_FIELDS definition from CSV file with local timestamp added
VBO_FIELDS_GTRlts='
# RC field-      format  smooth         VBO           VBO column
# name part       spec     it        header name         name        comment
#------------  ----------  ---  --------------------  ---------  ----------------
         UTC ,   %010.3f ,  0 ,                time
#
      AccelX ,    %+5.2f ,  0 ,         lat accel g ,   latacc
      AccelY ,    %+5.2f ,  0 ,        long accel g ,  longacc
#
    Latitude ,  %+010.4f ,  0 ,            latitude ,      lat
   Longitude ,  %+011.4f ,  0 ,           longitude ,     long
       Speed ,    %06.2f ,  0 ,        velocity kmh , velocity
    Altitude ,   %+09.2f ,  0 ,              height
     GPSSats ,      %02d ,  0 ,          satellites ,     sats
#
         RPM ,      %05d ,  0 ,                 RPM
         TPS ,      %03d ,  0 ,         ECUthrottle
#
 SessionTime ,    %07.3f ,  0 ,             heading
# --- these are my additional fields
        Gsum ,   %+05.2f ,  0 ,
#
  WheelSpeed ,    %05.1f ,  0 ,
  EngineLoad ,    %05.1f ,  0 ,
  EngineTemp ,    %05.1f ,  0 ,
#
    PredTime ,    %08.4f ,  0 ,
#
    Interval ,        %d ,  0 ,          RCinterval
'
#
# my GTR without predicted lap time VBO_FIELDS definition
VBO_FIELDS_GTR_noPred='
# RC field-      format  smooth         VBO           VBO column
# name part       spec     it        header name         name        comment
#------------  ----------  ---  --------------------  ---------  ----------------
         Utc ,   %010.3f ,  0 ,                time ,          , in VBO it is HHMMSS.ms
#
      AccelX ,    %+5.2f ,  0 ,         lat accel g ,   latacc
      AccelY ,    %+5.2f ,  0 ,        long accel g ,  longacc
#
    Latitude ,  %+010.4f ,  0 ,            latitude ,      lat
   Longitude ,  %+011.4f ,  0 ,           longitude ,     long
       Speed ,    %06.2f ,  0 ,        velocity kmh , velocity
    Altitude ,   %+09.2f ,  0 ,              height
     GPSSats ,      %02d ,  0 ,          satellites ,     sats
#
         RPM ,      %05d ,  0 ,                 RPM
         TPS ,      %03d ,  0 ,         ECUthrottle
#
 SessionTime ,    %07.3f ,  0 ,             heading ,          , calc from GPS lat/long
# --- these are my additional fields
      AccelZ ,   %+06.2f ,  0 ,
        Gsum ,   %+05.2f ,  0 ,
#
  WheelSpeed ,    %05.1f ,  0 ,
  EngineLoad ,    %05.1f ,  0 ,
  EngineTemp ,    %05.1f ,  0 ,
#
    Interval ,        %d ,  0 ,          RCinterval
'
#
# my C63 S VBO_FIELDS definition
VBO_FIELDS_C63S='
# RC field-      format  smooth         VBO           VBO column
# name part       spec     it        header name         name        comment
#------------  ----------  ---  --------------------  ---------  ----------------
         Utc ,   %010.3f ,  0 ,                time ,          , in VBO it is HHMMSS.ms
#
      AccelX ,    %+5.2f ,  0 ,         lat accel g ,   latacc
      AccelY ,    %+5.2f ,  0 ,        long accel g ,  longacc
#
    Latitude ,  %+010.4f ,  0 ,            latitude ,      lat
   Longitude ,  %+011.4f ,  0 ,           longitude ,     long
       Speed ,    %06.2f ,  0 ,        velocity kmh , velocity
    Altitude ,   %+09.2f ,  0 ,              height
     GPSSats ,      %02d ,  0 ,          satellites ,     sats
#
         RPM ,      %05d ,  0 ,                 RPM
         TPS ,      %03d ,  0 ,         ECUthrottle
#
 SessionTime ,    %07.3f ,  0 ,             heading ,          , calc from GPS lat/long
# --- these are my additional fields
      AccelZ ,   %+06.2f ,  0 ,
        Gsum ,   %+05.2f ,  0 ,
#
  WheelSpeed ,    %05.1f ,  0 ,
  EngineLoad ,    %05.1f ,  0 ,
  EngineTemp ,    %05.1f ,  0 ,
#
    Interval ,        %d ,  0 ,          RCinterval
'
#
# my C63 S without RC accelerometer VBO_FIELDS definition
VBO_FIELDS_C63Sna='
# RC field-      format  smooth         VBO          VBO column
# name part       spec     it        header name         name        comment
#------------  ----------  ---  --------------------  ---------  ----------------
         Utc ,   %010.3f ,  0 ,                time ,          , in VBO it is HHMMSS.ms
#
    Latitude ,  %+010.4f ,  0 ,            latitude ,      lat
   Longitude ,  %+011.4f ,  0 ,           longitude ,     long
       Speed ,    %06.2f ,  0 ,        velocity kmh , velocity
    Altitude ,   %+09.2f ,  0 ,              height
     GPSSats ,      %02d ,  0 ,          satellites ,     sats
#
         RPM ,      %05d ,  0 ,                 RPM
         TPS ,      %03d ,  0 ,         ECUthrottle
#
 SessionTime ,    %07.3f ,  0 ,             heading ,          , calc from GPS lat/long
# --- these are my additional fields
  WheelSpeed ,    %05.1f ,  0 ,
  EngineLoad ,    %05.1f ,  0 ,
  EngineTemp ,    %05.1f ,  0 ,
#
    Interval ,        %d ,  0 ,          RCinterval
'

# a default <fields suffix> string value can be set ... the usage precedence is:
# - if no "-n <fields suffix>" argument was provided, use the value of
#   DEFAULT_VBO_FIELDS_SUFFIX as the <fields suffix>
# AND
# - if the value of DEFAULT_VBO_FIELDS_SUFFIX is empty, use the value 'MIN'
#DEFAULT_VBO_FIELDS_SUFFIX=''     # same effect as value MIN
#DEFAULT_VBO_FIELDS_SUFFIX='MIN'  # same effect as empty value
#DEFAULT_VBO_FIELDS_SUFFIX='GTR'  # default for "raw" RaceCapture log file
DEFAULT_VBO_FIELDS_SUFFIX='GTRlts'  # default for RC log w/local timestamp added

# indicate whether the latitudinal/longitudinal acceleration G-forces are to be
# reversed -- the configuration/position of the RaceCapture unit can reverse
# these fields (NOTE that this setting is itself reversed by the -r argument)
REVERSE_ACCEL_XY='1'  # change the 0 to 1 to reverse lat/long acceleration Gs

# the data-smoothing alpha is between 0 and 1 where alpha=1 yields no smoothing
# (normally, this data-smoothing is not real useful)
ALPHA_FOR_DATA_SMOOTHING=0.75

# VBO files may or may not work if you put the column names on a single line --
# if not '1', then format column names as 1 per line instead of all on 1 line
PUT_COLUMN_NAMES_ON_1_LINE='1'

# define the time-zone offset from UTC time -- if TIME_ZONE_OFFSET is the empty
# string ('' or ""), use the system's time-zone offset value
# (NOTE that this is overridden by the "-t <[-][H]HMM>" argument)
#TIME_ZONE_OFFSET=''      # use the OS-supplied timezone
TIME_ZONE_OFFSET='-700'  # Pacific Daylight Time
#TIME_ZONE_OFFSET='-800'  # Pacific Standard Time
#
#+++++ END: USER CONFIGURATION AREA ++++++++++++++++++++++++++++++++++++++++++++

# *** IMPORTANT : don't start other variable names with VBO_FIELDS_

# help prevent "sed: RE error: illegal byte sequence" errors (OS X 10.8!)
LC_CTYPE=C

# "macros" -- well, conceptually, at least
bON=`/usr/bin/tput bold`  # set terminal to bold
bOFF=`/usr/bin/tput rmso`  # set terminal to non-bold
BEL=`/usr/bin/tput bel`  # ring terminal's bell or flash, if set to visual bell

# trap interrupts and cancel processing
trap 'doInterrupt; exit 255' TERM INT
#-------------------------------------------------------------------------------
# handle some interrupts
function doInterrupt()
{
	echo "\nProcessing interrupted -- quitting.${BEL}\n"
	/bin/rm -f "$WORK_FILE_PATHNAME"
}
#-------------------------------------------------------------------------------

# option defaults
SIMPLIFY_DATA='1'  # by default, ignore data lines that have no GPS data
SHOW_ENTIRE_CSV_FIELDS_LIST='0'
SHOW_SELECTED_CSV_FIELDS_INFO='0'
THE_VBO_FIELDS_NAME_SUFFIX=''
USE_DOS_EOL='0'

# get fields suffix(s) of currently defined "VBO_FIELDS_<fields suffix>" values
DEFINED_VBO_FIELDS_NAME_SUFFIXES=`
 /usr/bin/grep '^[\ \	]*VBO_FIELDS_[a-zA-Z0-9_][a-zA-Z0-9_]*\=' "$0" | \
  /usr/bin/sed -e 's/^[\ \	]*VBO_FIELDS_\([a-zA-Z0-9_][a-zA-Z0-9_]*\)\=.*$/\1/' |\
   /usr/bin/tr "\n" ','`
#echo "\nDEFINED_VBO_FIELDS_NAME_SUFFIXES = '$DEFINED_VBO_FIELDS_NAME_SUFFIXES'"

# quick 'n dirty argument/options parsing
while true
do
	if test "$1" = '-h'
	then
		echo '
'"${bON}"'Description'"${bOFF}"':
This script reads a CSV file that was generated by RaceCapture and converts it
to a VBO file that can be processed by RaceLogic'"'"'s Circuit Tools application.

The results file is named <processed file name>.vbo

Synopsis:  (square brackets means "is optional")
'"${bON}"'rcCSVtoVBO.sh'"${bOFF}"' [[-h] [-hu] [-i] [-r] [-s] [-v] [-n <fields suffix>] [-t <[-][H]HMM>] [-w]] [<path to RaceCapture CSV file>]

Where:  (last argument must appear last, if provided)
'"${bON}"'-h'"${bOFF}"' : shows this help message, then quits
'"${bON}"'-hu'"${bOFF}"' : shows this script'"'"'s internal '"${bON}"'USER CONFIGURATION AREA'"${bOFF}"' content, then quits
'"${bON}"'-i'"${bOFF}"' : shows the complete list of CSV fields and their left-to-right numbered
     position, then quits (the list is also shown with the -v option)
'"${bON}"'-r'"${bOFF}"' : reverses the lat/long acceleration G fields (depending upon the RaceCapture
     setup, these two fields may be switched)
'"${bON}"'-s'"${bOFF}"' : avoids the "simplification"/reduction of the VBO data where all the entries
     that have no GPS (satellite count) field are ignored -- such entries are
     present if you use a higher-rate accelerometer setting and/or capture
     CAN/OBD data at a rate that is higher than your GPS ... this yields many
     non-GPS entries where those values will not be visible on the graphs shown
     in the Circuit Tools application
'"${bON}"'-v'"${bOFF}"' : shows information about the fields selected for the VBO file, then quits
     (useful when setting up VBO fields definitions inside rcCSVtoVBO.sh)
'"${bON}"'-n <fields suffix>'"${bOFF}"' : provides a suffix for a "VBO_FIELDS" variable that is
                     defined/hard-coded in the rcVSCtoVBO.sh script -- this
                     allows the definition of multiple VBO_FIELDS specificaitons
                     named "VBO_FIELDS_<fields suffix>" to be set up, selected
                     and used ... e.g., different "VBO_FIELDS" specifications can
                     be setup for different cars, since different cars often have
                     different CAN/OBD data available so the RaceCapture CSV
                     (log) file will have different fields (see the NOTEs below)
'"${bON}"'-t <[-][H]HMM>'"${bOFF}"' : overrides the current script-defined or system-supplied
                 time-zone offset from UTC (must be a string formatted as [-]HMM)
'"${bON}"'-w'"${bOFF}"' : generate the VBO file with DOS/Windows-style line endings (instead of UNIX)
'"${bON}"'<path to RaceCapture CSV file>'"${bOFF}"' : provides the name of the CSV file to be
                                 processed -- if this option is missing, a
                                 file pathname must be supplied, interactively
'"${bON}"'NOTEs'"${bOFF}"':
- extensive user-configuration is allowed by setting values in the rcCSVToVBO.sh
  script ... see the section: "'"${bON}"'USER CONFIGURATION AREA'"${bOFF}"'"

- this script uses the extractCSVdata.sh script and assumes a CSV format where
  commas always/only separate fields

- the <fields suffix> must only have alphabetic, alphanumeric and/or underscore (_)
  characters ... others will be ignored/removed

- the currently defined <fields suffix> values are (MIN is the built-in minimum spec):'
		CURRENT_SUFFIXES=`echo "$DEFINED_VBO_FIELDS_NAME_SUFFIXES" | \
													/usr/bin/sed -e 's/,$//' -e 's/,/ , /g'`

		if test "$CURRENT_SUFFIXES" = ''
		then
			echo '  (none)'
		else
			echo "  ${bON}$CURRENT_SUFFIXES${bOFF}"
		fi

		exit
	fi

	if test "$1" = '-hu'
	then
		/usr/bin/sed -e '/START\: USER CONFIG/,/END\: USER CONFIG/ !d' "$0" | \
			/usr/bin/sed -e 's/^\([A-Z][A-Za-z0-9_]*\)=/'"${bON}"'\1'"${bOFF}"'=/'
		exit
	fi

	if test "$1" = '-i'
	then
		SHOW_ENTIRE_CSV_FIELDS_LIST='1'
		shift
		continue
	fi

	if test "$1" = '-r'
	then
		if test "$REVERSE_ACCEL_XY" != '1'
		then
			REVERSE_ACCEL_XY='1'
		else
			REVERSE_ACCEL_XY='0'
		fi
		shift
		continue
	fi

	if test "$1" = '-s'
	then
		SIMPLIFY_DATA='0'
		shift
		continue
	fi

	if test "$1" = '-v'
	then
		SHOW_SELECTED_CSV_FIELDS_INFO='1'
		shift
		continue
	fi

	if test "$1" = '-n'
	then
		shift
		THE_VBO_FIELDS_NAME_SUFFIX=`
					echo "$1" | /usr/bin/sed -e 's/^[\ \	]*//' -e 's/[\ \	]*$//'`
		shift
		continue
	fi

	if test "$1" = '-t'
	then
		shift
		TIME_ZONE_OFFSET="$1"
		shift
		continue
	fi

	if test "$1" = '-w'
	then
		USE_DOS_EOL='1'
		shift
		continue
	fi

	# if required, get the full pathname for a file that contains line-item entries
	# in CSV format where the first such entry is a field-header entry.
	if test "$1" = ''
	then
		echo '
This script reads a RaceCapture CSV file and outputs a VBO file for Circuit Tools.
-----
Enter the full pathname for the RaceCapture CSV (log) file: '
		read CSV_FILE_PATHNAME

		if test ! -f "$CSV_FILE_PATHNAME"
		then
			echo
			echo 'The CSV file'
			echo "$CSV_FILE_PATHNAME"
			echo 'could not be found.'
			echo
			CSV_FILE_PATHNAME=''
			continue
		fi
	else
		CSV_FILE_PATHNAME="$1"

		if test ! -f "$CSV_FILE_PATHNAME"
		then
			echo
			echo 'The CSV file'
			echo "$CSV_FILE_PATHNAME"
			echo 'could not be found.'
			echo
			CSV_FILE_PATHNAME=''
			shift
			continue
		fi
	fi

	break
done

echo "\nGathering info ..."

# determine the <fields suffix> for the name of the VBO_FIELDS specification
if test "$THE_VBO_FIELDS_NAME_SUFFIX" = ''
then
	if test "$DEFAULT_VBO_FIELDS_SUFFIX" != ''
	then
		THE_VBO_FIELDS_NAME_SUFFIX="$DEFAULT_VBO_FIELDS_SUFFIX"
	else
		THE_VBO_FIELDS_NAME_SUFFIX='MIN'
	fi
fi

# validate the <fields suffix> value
THE_VBO_FIELDS_NAME_SUFFIX=`echo "$THE_VBO_FIELDS_NAME_SUFFIX" | \
														/usr/bin/sed -e 's/[^a-zA-Z0-9_]//g'`
#echo "THE_VBO_FIELDS_NAME_SUFFIX = '$THE_VBO_FIELDS_NAME_SUFFIX'"

if test "`echo $DEFINED_VBO_FIELDS_NAME_SUFFIXES | \
										/usr/bin/grep $THE_VBO_FIELDS_NAME_SUFFIX,`" = ''
then
	echo "\nERROR: there is no 'VBO_FIELDS_$THE_VBO_FIELDS_NAME_SUFFIX' specification ... quitting${BEL}"
	exit 1
fi

# determine the time-zone offset value (in the following order):
# - if provided via the -t parameter, use that value
# - if a non-empty DEFAULT_TIME_ZONE_OFFSET was provided, use that value
# - use the system's time-zone offset value
if test "$TIME_ZONE_OFFSET" = ''
then
	TIME_ZONE_OFFSET=`/bin/date +%z`
else
	# do some basic validation of the time-zone offset value
	MSG="\nERROR: the time-zone offset '$TIME_ZONE_OFFSET' is not valid ... quitting${BEL}"

	if test \
		\( "`echo $TIME_ZONE_OFFSET | \
									/usr/bin/grep ^[+\-]*[0-9][0-9][0-9]$`" = '' \) -a \
		\( "`echo $TIME_ZONE_OFFSET | \
									/usr/bin/grep ^[+\-]*[0-9][0-9][0-9][0-9]$`" = '' \)
	then
		echo "$MSG"
		exit 1
	else
		if test \( $TIME_ZONE_OFFSET -gt 2400 \) -o \
				  \( $TIME_ZONE_OFFSET -lt -2400 \)
		then
			echo "$MSG"
			exit 1
		fi
	fi
fi

# compute the time-zone offset in seconds
TZ_OFFSET_HRS=`echo "$TIME_ZONE_OFFSET" | \
												/usr/bin/sed -e 's/^\(.*\)[0-9][0-9]$/\1/'`
TZ_OFFSET_MINS=`echo "$TIME_ZONE_OFFSET" | \
												/usr/bin/sed -e 's/^.*\([0-9][0-9]\)$/\1/'`
TZ_OFFSET_SEC=`\
			/bin/expr \( $TZ_OFFSET_HRS \* 3600 \) + \( $TZ_OFFSET_MINS \* 60 \)`

CSV_FILE_NAME=`/usr/bin/basename "$CSV_FILE_PATHNAME" | \
														/usr/bin/sed -e 's/^\(.*\)\..*$/\1/'`
CSV_DIR=`/usr/bin/dirname "$CSV_FILE_PATHNAME"`
CSV_FILE_SUFFIX=`/usr/bin/basename "$CSV_FILE_PATHNAME" | \
														/usr/bin/sed -e 's/^.*\.\(.*\)$/\1/'`

# the name of the file that will be created by extractCSVdata.sh
WORK_FILE_PATHNAME="$CSV_DIR/${CSV_FILE_NAME} - VBO.$CSV_FILE_SUFFIX"

if test "`which extractCSVdata.sh | /usr/bin/fgrep extractCSVdata.sh`" = ''
then
	echo "\nERROR: can't find the executable script 'extractCSVdata.sh' ... quitting${BEL}"
	exit 1
fi

# get the CSV file's field list and, if required, show them then exit
SOURCE_CSV_FIELD_NUM_NAME_LIST=`
				extractCSVdata.sh -iq "$CSV_FILE_PATHNAME" | /usr/bin/sed -e '1,9d'`

if test "$SHOW_ENTIRE_CSV_FIELDS_LIST" = '1'
then
	echo
	echo 'The complete list of fields in the CSV file ...'
	echo ' Field  Field'
	echo 'Number  Name'
	echo '------  -------------------'
	echo "$SOURCE_CSV_FIELD_NUM_NAME_LIST"
	exit
fi
#echo "\nSOURCE_CSV_FIELD_NUM_NAME_LIST:\n'$SOURCE_CSV_FIELD_NUM_NAME_LIST'\n"

# remove the whitespace from the CSV fields and change EOL character
# NOTE: the first sed filter specifically handles 2 formats:
#       - the "normal"/raw RaceCapture log with double-quotes and vertical bars
#         in the field names
#       - a pre-processed RaceCapture log that has simplified field names
SOURCE_CSV_FIELD_NUM_NAME_LIST=`
				echo "$SOURCE_CSV_FIELD_NUM_NAME_LIST" | \
					/usr/bin/sed -e 's/^ *\([0-9][0-9]*\)  *"\(.*\)"\|.*$/\1,\2/' \
									 -e 's/^ *\([0-9][0-9]*\)  *\(.*\)$/\1,\2/' | \
						/usr/bin/sed -e 's/^\(.*\)"\|".*$/\1/' | \
							/usr/bin/tr "\n" '~' | \
								/usr/bin/sed -e 's/~$//'`
#echo "\nSOURCE_CSV_FIELD_NUM_NAME_LIST (unflattened)"
#echo "$SOURCE_CSV_FIELD_NUM_NAME_LIST" | /usr/bin/tr '~' "\n"

if test "$SOURCE_CSV_FIELD_NUM_NAME_LIST" = ''
then
	echo "ERROR: no source CSV fields were found (this may be a bug) ... quitting${BEL}"
	exit 1
fi

# get the named/suffix'd VBO_FIELDS specification
THE_VBO_FIELDS_SPEC=`eval echo \"\\$VBO_FIELDS_\$THE_VBO_FIELDS_NAME_SUFFIX\"`
#echo "\nTHE_VBO_FIELDS_NAME_SUFFIX = '$THE_VBO_FIELDS_NAME_SUFFIX'"
#echo "\nTHE_VBO_FIELDS_SPEC:\n'$THE_VBO_FIELDS_SPEC'\n"

# remove the whitespace and invalid entries from the VBO_FIELDS specification
# and change the EOL character and, if required, reverse the AccelX and AccelY
# RC fields
if test "$REVERSE_ACCEL_XY" = '1'
then
	THE_VBO_FIELDS_SPEC=`echo "$THE_VBO_FIELDS_SPEC" | /usr/bin/tr "\t" ' ' |  \
												/usr/bin/sed -e 's/	/ /g'                \
																 -e '/^[ ]*#/d'               \
																 -e 's/^[ ]*//'               \
																 -e 's/[ ]*,/,/g'             \
																 -e 's/,[ ]*/,/g'             \
																 -e 's/[ ]*$//'               \
																 -e '/^,/d'                   \
																 -e '/^$/d'                   \
																 -e 's/AccelX/Accel1/g' |     \
													/usr/bin/tr "\n" '~' |                 \
														/usr/bin/sed -e 's/AccelY/AccelX/g' \
																		 -e 's/Accel1/AccelY/g' \
																		 -e 's/~$//'`
else
	THE_VBO_FIELDS_SPEC=`echo "$THE_VBO_FIELDS_SPEC" | /usr/bin/tr "\t" ' ' | \
															/usr/bin/sed -e 's/	/ /g'      \
																			 -e '/^[ ]*#/d'     \
																			 -e 's/^[ ]*//'     \
																			 -e 's/[ ]*,/,/g'   \
																			 -e 's/,[ ]*/,/g'   \
																			 -e 's/[ ]*$//'     \
																			 -e '/^,/d'         \
																			 -e '/^$/d' |       \
																/usr/bin/tr "\n" '~' |       \
																	/usr/bin/sed -e 's/~$//'`
fi
#echo "\nTHE_VBO_FIELDS_SPEC\n'$THE_VBO_FIELDS_SPEC'\n" | /usr/bin/tr '~' "\n"

# create a comma-separated list of the selected CSV field numbers by looking up
# the number of each CSV field that corresponds to the RC field names in the
# VBO_FIELDS specification
SELECTED_CSV_FIELD_NUMS=`\
	/usr/bin/awk -v sourceCSVfieldsNumNameList="$SOURCE_CSV_FIELD_NUM_NAME_LIST" '
		BEGIN {
			# SOURCE_CSV_FIELD_NUM_NAME_LIST may contain double-quotes
			# NOTE: since SOURCE_CSV_FIELD_NUM_NAME_LIST may contain double-quotes,
			#       it must be loaded via the -v argument (unless quotes are
			#       escaped)

			# create an array of CSV-field entries and get the number of fields
			numSourceCSVfields = split(sourceCSVfieldsNumNameList, \
												sourceCSVfieldNumNameEntry, "~")

			# create an associative array to enable a CSV field number to be looked
			# up (i.e., indexed) by a CSV field name so:
			# CSVfieldNum[<CSV-field-name>] is the <CSV-field-number>
			for (i = 1; i <= numSourceCSVfields; i++) {
				split(sourceCSVfieldNumNameEntry[i], CSVfieldNumName, ",")
				CSVfieldNum[CSVfieldNumName[2]] = CSVfieldNumName[1] #name > field #
			}

			# load the selected/named VBO_FIELDS specification into awk
			theFIELDSspec = "'"$THE_VBO_FIELDS_SPEC"'"

			# create an array of fields-spec entries & get the number of entries
			numFIELDSspecEntries = split(theFIELDSspec, FIELDSspecEntry, "~")

			# output a comma-separated list of the number/position of each CSV
			# field that corresponds to the RaceCapture field name in the
			# VBO_FIELDS specification
			isFirstField = 1
			thisCSVfieldNum = -1

			for (i = 1; i <= numFIELDSspecEntries; i++) {
				split(FIELDSspecEntry[i], FIELDSspecInfo, ",")

				# get RaceCapture field-name part from the VBO_FIELDS specification
				fieldNamePart = FIELDSspecInfo[1]

				# find the corresponding full/original CSV field name
				prevIndex = 999
				prevPortionMatched = 0 ;  # prev portion of source CSV name matched
				theFoundCSVfieldName = ""

				# find "best match" (see below for meaning of "best match")
				for (j = 1; j <= numSourceCSVfields; j++) {
					portionOfCSVname = 0.0;
					split(sourceCSVfieldNumNameEntry[j], CSVfieldNumName, ",")
					fullCSVfieldName = CSVfieldNumName[2]

					# check for exact match
					if (fullCSVfieldName == fieldNamePart) {
						theFoundCSVfieldName = fullCSVfieldName
						break
					}

					# search for a match of the <field name part> in the source
					# CSV field name
					currIndex = index(fullCSVfieldName, fieldNamePart)

					if (currIndex != 0) {
						# determine the portion the source CSV field name that the
						# <field name part> constitutes
						portionOfCSVname = \
									length(fieldNamePart) / length(fullCSVfieldName)

						# if applicable, update the "found field name" if this is a
						# "better" match -- i.e.:
						# - the current match is more left-most than the previous
						#   match (or non-match)
						#   OR :
						# - the current match is as left-most as the previous match
						#   (or non-match) AND
						# - the current <field name part> is a greater portion of
						#   the source CSV field name that was the previous match
						#   (or non-match)
						if ((currIndex < prevIndex) ||
							 ((currIndex == prevIndex) &&
							  (portionOfCSVname > prevPortionMatched))) {
							prevIndex = currIndex
							prevPortionMatched = portionOfCSVname
							theFoundCSVfieldName = fullCSVfieldName
						}
					}
				}

				# get the number/position of the CSV field that corresponds to
				# the name of the CSV field name from the CSV_FIELDS spec
				thisCSVfieldNum = CSVfieldNum[theFoundCSVfieldName]
				# for debug ... fails if names have quote-requiring characters
				#system("echo "fieldNamePart" x "theFoundCSVfieldName" >&2")

				if (isFirstField == 1) { isFirstField = 0 }
				else { printf(",") }

				if (length(thisCSVfieldNum) == 0) {
					system("echo >&2 ; echo ERROR: the CSV file has no field matching the field name part \\\\\""fieldNamePart"\\\\\" >&2")
					thisCSVfieldNum = -1
				}

				printf("%d", thisCSVfieldNum)
			}

			printf("\n")
		}'`

if test "`echo $SELECTED_CSV_FIELD_NUMS | /usr/bin/fgrep -e '-1'`" != ''
then
	echo "\nError(s) encountered ... quitting${BEL}"
	exit 1
fi
#echo "\nSELECTED_CSV_FIELD_NUMS = '$SELECTED_CSV_FIELD_NUMS'\n"

VBO_HEADER_NAMES=`\
	/usr/bin/awk '
		BEGIN {
			# load the selected/named VBO_FIELDS specification into awk
			theFIELDSspec = "'"$THE_VBO_FIELDS_SPEC"'"

			# create an array of fields-spec entries and get the number of fields
			numFIELDSspecEntries = split(theFIELDSspec, FIELDSspecEntry, "~")

			# output a newline-separated list of the VBO [header] entries from the
			# VBO_FIELDS specification
			isFirstField = 1
			thisVBOheaderName = ""

			for (i = 1; i <= numFIELDSspecEntries; i++) {
				split(FIELDSspecEntry[i], FIELDSspecInfo, ",")

				# get the VBO field name ... if it is empty, use the RC field name
				if (length(FIELDSspecInfo[4]) != 0) {
					thisVBOheaderName = FIELDSspecInfo[4]
				}
				else { thisVBOheaderName = FIELDSspecInfo[1] }

				if (isFirstField == 1) { isFirstField = 0 }
				else { printf("\n") }

				printf("%s", thisVBOheaderName)
			}

			printf("\n")
		}'`
#echo "\nVBO_HEADER_NAMES:\n'$VBO_HEADER_NAMES'\n"

VBO_COLUMN_NAMES=`\
	/usr/bin/awk '
		BEGIN {
			# load the selected/named VBO_FIELDS specification into awk
			theFIELDSspec = "'"$THE_VBO_FIELDS_SPEC"'"

			# create an array of fields-spec entries and get the number of fields
			numFIELDSspecEntries = split(theFIELDSspec, FIELDSspecEntry, "~")

			# output a space-separated list of the VBO [column name] entries from
			# the VBO_FIELDS spec
			isFirstField = 1
			thisVBOcolumnName = ""

			for (i = 1; i <= numFIELDSspecEntries; i++) {
				split(FIELDSspecEntry[i], FIELDSspecInfo, ",")

				# get the VBO column name ... if it is empty, use the VBO header
				# name and, if that is empty, use the RC field name
				if (length(FIELDSspecInfo[5]) != 0) {
					thisVBOcolumnName = FIELDSspecInfo[5]
				}
				else if (length(FIELDSspecInfo[4]) != 0) {
					thisVBOcolumnName = FIELDSspecInfo[4]
				}
				else { thisVBOcolumnName = FIELDSspecInfo[1] }

				if (isFirstField == 1) { isFirstField = 0 }
				else { printf(" ") }  # for now, put column names all on 1 line

				printf("%s", thisVBOcolumnName)
			}

			printf("\n")
		}'`
#echo "\nVBO_COLUMN_NAMES:\n'$VBO_COLUMN_NAMES'\n"

# get the VBO field-formats from the VBO fields specification
VBO_FIELD_FORMATS=`\
	/usr/bin/awk '
		BEGIN {
			# load the selected/named VBO_FIELDS specification into awk
			theFIELDSspec = "'"$THE_VBO_FIELDS_SPEC"'"

			# create an array of field-spec entries and get the number of fields
			numFIELDSspecEntries = split(theFIELDSspec, FIELDSspecEntry, "~")

			# output a comma-separated list of the field-formatting entry from the
			# VBO_FIELDS specification
			isFirstField = 1
			thisFieldFormat = ""

			for (i = 1; i <= numFIELDSspecEntries; i++) {
				split(FIELDSspecEntry[i], FIELDSspecInfo, ",")

				# get the field-formatting entry ... if it is empty, use "%s"
				if (length(FIELDSspecInfo[2]) != 0) {
					thisFieldFormat = FIELDSspecInfo[2]
				}
				else { thisFieldFormat = "%s" }

				if (isFirstField == 1) { isFirstField = 0 }
				else { printf(",") }

				printf("%s", thisFieldFormat)
			}

			printf("\n")
		}'`
#echo "\nVBO_FIELD_FORMATS: '$VBO_FIELD_FORMATS'\n"

# get the VBO data-smoothing flags from the VBO fields specification
VBO_DATA_SMOOTHING_FLAGS=`\
	/usr/bin/awk '
		BEGIN {
			# load the selected/named VBO_FIELDS specification into awk
			theFIELDSspec = "'"$THE_VBO_FIELDS_SPEC"'"

			# create an array of field-spec entries and get the number of fields
			numFIELDSspecEntries = split(theFIELDSspec, FIELDSspecEntry, "~")

			# output a comma-separated list of the data-smoothing flags from the
			# VBO_FIELDS specification
			isFirstField = 1
			thisDataSmoothingFlag = 0

			for (i = 1; i <= numFIELDSspecEntries; i++) {
				split(FIELDSspecEntry[i], FIELDSspecInfo, ",")

				# get data-smoothing flag value ... if it is empty or not 1, use 0
				if ((length(FIELDSspecInfo[3]) == 0) ||
					 (FIELDSspecInfo[3] != 1)) { thisDataSmoothingFlag = 0 }
				else { thisDataSmoothingFlag = FIELDSspecInfo[3] }

				if (isFirstField == 1) { isFirstField = 0 }
				else { printf(",") }

				printf("%d", thisDataSmoothingFlag)
			}

			printf("\n")
		}'`
#echo "\nVBO_DATA_SMOOTHING_FLAGS:\n'$VBO_DATA_SMOOTHING_FLAGS'\n"

# create a sort list so the CSV entries can be sorted by the UTC field
SORT_LIST=''
SEP=''

for ENTRY in `echo "$VBO_HEADER_NAMES" | /usr/bin/tr ',' "\n"`
do
	if test "$ENTRY" = 'time'
	then
		SORT_LIST="${SORT_LIST}${SEP}1"
		break
	else
		SORT_LIST="${SORT_LIST}${SEP}0"
	fi

	SEP=','
done
#echo "SORT_LIST:\n'$SORT_LIST'\n"

# if required, output info about the fields selected for the VBO file
if test "$SHOW_SELECTED_CSV_FIELDS_INFO" = '1'
then
	# get the CSV field names from the VBO fields specification
	CSV_FIELD_NAMES=`\
		/usr/bin/awk '
			BEGIN {
				# load the selected/named VBO_FIELDS specification into awk
				theFIELDSspec = "'"$THE_VBO_FIELDS_SPEC"'"

				# create an array of field-spec entries and get the number of fields
				numFIELDSspecEntries = split(theFIELDSspec, FIELDSspecEntry, "~")

				# output a comma-separated list of the RC/CSV field names from the
				# VBO_FIELDS specification
				isFirstField = 1
				thisCSVname = 0

				for (i = 1; i <= numFIELDSspecEntries; i++) {
					split(FIELDSspecEntry[i], FIELDSspecInfo, ",")
					thisCSVname = FIELDSspecInfo[1]

					if (isFirstField == 1) { isFirstField = 0 }
					else { printf(",") }

					printf("%s", thisCSVname)
				}

				printf("\n")
			}'`
	#echo "CSV_FIELD_NAMES:\n'$CSV_FIELD_NAMES'\n"

	echo
	echo "\nThe complete list of fields in the source CSV file ..."
	echo ' Field  Field'
	echo 'Number  Name'
	echo '------  -------------------'
	echo "$SOURCE_CSV_FIELD_NUM_NAME_LIST" | \
								/usr/bin/tr '~' "\n" | \
								/usr/bin/awk -F ',' '{ printf("%5d   %s\n", $1, $2) }'
	echo
	echo "The VBO_FIELDS suffix value: $THE_VBO_FIELDS_NAME_SUFFIX"
	echo
	echo 'The list of CSV field names selected via the VBO spec:'
	echo "$CSV_FIELD_NAMES" | /usr/bin/sed -e 's/,/ , /g'
	echo 
	echo 'The list of CSV field numbers selected via the VBO spec:'
	echo "$SELECTED_CSV_FIELD_NUMS"
	echo 
	echo 'The list of [header] names via the VBO spec:'
	echo "$VBO_HEADER_NAMES" | /usr/bin/tr "\n" "," | /usr/bin/sed -e 's/,/ , /g'
	echo 
	echo 'The list of [column names] via the VBO spec:'
	echo "$VBO_COLUMN_NAMES" | /usr/bin/sed -e 's/ / , /g'
	echo 
	echo 'The list of field formats via data in the VBO spec:'
	echo "$VBO_FIELD_FORMATS" | /usr/bin/sed -e 's/,/ , /g'
	echo 
	echo 'The list of data-smoothing flags via data in the VBO spec:'
	echo "$VBO_DATA_SMOOTHING_FLAGS"
	echo 
	echo 'The sort list for the CSV entries extracted for the VBO file:'
	echo "$SORT_LIST"
	exit
fi

# create a CSV file containing only the desired fields
echo "\nGenerating a CSV work file: ${CSV_FILE_NAME} - VBO.$CSV_FILE_SUFFIX) ..."
extractCSVdata.sh -fl "$SELECTED_CSV_FIELD_NUMS" -so "$SORT_LIST" \
										-fr "$SORT_LIST" -rs ' - VBO' "$CSV_FILE_PATHNAME"

if test ! -f "$CSV_DIR/${CSV_FILE_NAME} - VBO.$CSV_FILE_SUFFIX"
then
	echo "\nERROR: the file"
	echo "$CSV_DIR/${CSV_FILE_NAME} - VBO.$CSV_FILE_SUFFIX"
	echo "was not generated by the script 'extractCSVdata.sh' ... quitting${BEL}"
	exit 1
fi

# get the 1st date/time entry (without millisecs) from the generated work file:
# - get the first 2 lines (header and first record)
# - remove the header line
# - extract the first field (UTC enum) value then add the timezone offset secs
U_DATE=`
	/usr/bin/head -n 2 "$CSV_DIR/${CSV_FILE_NAME} - VBO.$CSV_FILE_SUFFIX" | \
	/usr/bin/sed -e '1d' -e s'/^ *\([0-9][0-9]*\)[0-9][0-9][0-9],.*$/\1/'`
U_DATE=`/bin/expr $U_DATE + $TZ_OFFSET_SEC`

# save current IFS (inter-field separator)
IFS_SAVE=$IFS

# set IFS to be a comma, only
IFS=','

# set simplified data phrase
if test "$SIMPLIFY_DATA" = '1'
then
	SIMPLIFIED_DATA_PHRASE='(with simplified data) '
else
	SIMPLIFIED_DATA_PHRASE=''
fi

# create the VBO file
echo "\n${bON}Generating the VBO file${bOFF} ${SIMPLIFIED_DATA_PHRASE}..."
VBO_FILE_PATHNAME="$CSV_DIR/${CSV_FILE_NAME}.vbo"
/bin/echo -n > "$VBO_FILE_PATHNAME"

# add the creation-date information
/bin/echo -n 'File created on ' >> "$VBO_FILE_PATHNAME"
/bin/date -ujf "%s" "+%d/%m/%Y @ %H:%M:%S" $U_DATE >> "$VBO_FILE_PATHNAME"
echo >> "$VBO_FILE_PATHNAME"

# add the header information
echo '[header]' >> "$VBO_FILE_PATHNAME"
echo "$VBO_HEADER_NAMES" >> "$VBO_FILE_PATHNAME"
echo >> "$VBO_FILE_PATHNAME"

# add some comments
echo '[comments]' >> "$VBO_FILE_PATHNAME"
echo 'CSV-to-VBO translation by rcCSVtoVBO.sh' >> "$VBO_FILE_PATHNAME"

if test "$SIMPLIFY_DATA" = '1'
then
	echo 'Simplified data (non-GPS entries removed).' >> "$VBO_FILE_PATHNAME"
fi

echo '---' >> "$VBO_FILE_PATHNAME"
echo 'RaceLogic VBO coordinates are in Minutes:' >> "$VBO_FILE_PATHNAME"
echo ' lat: North is positive, South is negative.' >> "$VBO_FILE_PATHNAME"
echo 'long: West is positive, East is negative (opposite to RaceCapture CSV).' \
																			>> "$VBO_FILE_PATHNAME"

echo '---' >> "$VBO_FILE_PATHNAME"
echo 'RaceCapture CSV coordinates are in Degrees.MinutesSeconds:' \
																			>> "$VBO_FILE_PATHNAME"
echo ' lat: North is positive, South is negative.' >> "$VBO_FILE_PATHNAME"
echo 'long: West is negative, East is positive (opposite to RaceLogic VBO).' >> \
																				"$VBO_FILE_PATHNAME"

echo '---' >> "$VBO_FILE_PATHNAME"
echo "time: formatted as HHMMSS.sss (in timezone UTC $TIME_ZONE_OFFSET)" \
																			>> "$VBO_FILE_PATHNAME"
echo 'heading: in degrees where 0 = north, 90 = east, 180 = south, 270 = west' \
																			>> "$VBO_FILE_PATHNAME"

# add the "header" (AKA column names)
echo >> "$VBO_FILE_PATHNAME"
echo '[column names]' >> "$VBO_FILE_PATHNAME"

if test "$PUT_COLUMN_NAMES_ON_1_LINE" = '1'
then
	echo "$VBO_COLUMN_NAMES" >> "$VBO_FILE_PATHNAME"
else
	echo "$VBO_COLUMN_NAMES" | /usr/bin/tr ' ' "\n" >> "$VBO_FILE_PATHNAME"
fi

# add the data marker
echo >> "$VBO_FILE_PATHNAME"
echo "[data]\n" >> "$VBO_FILE_PATHNAME"

# transfer the data, transposing or computing the field values as required
/usr/bin/tail -n +2 "$WORK_FILE_PATHNAME" | \
	/usr/bin/sed -e '/^,,/d' | \
	/usr/bin/awk -F ',' '
		# defines leading/trailing whitespace-trimming functions
		# trim() may be used by the HEADER_PRINT_CMD and DATA_PRINT_CMD
		function ltrim(s) { sub(/^[ \t]+/, "", s); return s }
		function rtrim(s) { sub(/[ \t]+$/, "", s); return s }
		function trim(s) { return rtrim(ltrim(s)); }

		(NR == 1) {
			# load some shell variables into awk
			simplifyData = "'"$SIMPLIFY_DATA"'"
			columnNames = "'"$VBO_COLUMN_NAMES"'"
			fieldFormats = "'"$VBO_FIELD_FORMATS"'"
			dataSmoothingFlags = "'"$VBO_DATA_SMOOTHING_FLAGS"'"
			alphaDS = "'"$ALPHA_FOR_DATA_SMOOTHING"'"

			# create an array of VBO column names and get the number of fields
			# (this assumes that the first line is a field-header line)
			numColumnNames = split(columnNames, columnName, " ")

			# create an array of field formats
			split(fieldFormats, theFieldFormat, ",")

			# ensure there is a format specifier for all fields
			for (i = 1; i <= numColumnNames; i++) {
				if (length(theFieldFormat[i]) == 0) { theFieldFormat[i] = "%s" }
			}

			# create an array of field data-smoothing (DS) flags/indicators
			split(dataSmoothingFlags, doFieldDS, ",")

			# ensure there is a data-smoothing flag for all fields
			for (i = 1; i <= numColumnNames; i++) {
				if ((length(doFieldDS[i]) == 0) ||
					 (doFieldDS[i] != 1)) { doFieldDS[i] = 0 }
			}

			# create arrays to hold the current, the previous and the previous
			# data-smoothed field values
			for (i = 1; i <= numColumnNames; i++) {
				currVal[i] = ""
				prevVal[i] = ""
				prevDSval[i] = ""
			}

			# using the time-zone offset setting, determine the number of seconds
			# offset (+/-) from UTC
			tzOffset = "'"$TIME_ZONE_OFFSET"'"
			tzOffsetHrs = substr(tzOffset, 1, (length(tzOffset) - 2))
			tzOffsetMins = substr(tzOffset, (length(tzOffset) - 1))

			# could not get multiplying by -1 to create a negative value!!!
			if (tzOffsetHrs < 0) {
				secsOffset = (tzOffsetHrs * 3600) - (tzOffsetMins * 60)
			}
			else { secsOffset = (tzOffsetHrs * 3600) + (tzOffsetMins * 60) }

			# initialize some needed variables
			uDate = 0
			hrs = 0
			mins = 0
			secs = 0
			msecs = ""
			pi = 3.141592653589793238  # value of pi to 10 decimal places
			deg2radMult = pi / 180     # multiplier to convert degrees to radians
			rad2degMult = 180 / pi     # multiplier to convert radians to degrees
			currMinLat = 0.0
			currMinLong = 0.0
			currRadLat = pi
			currRadLong = pi
			prevRadLat = -1.0
			prevRadLong = -1.0
			headingRad = pi
			headingDeg = -1
			prevDSheadingDeg = -1
			intDeg = 0
			lineIsSkipped = 0
			dataOut = ""
			fieldSep = ""
			invalidLinesSkipped = 0
			initialLinesSkipped = 0
			nonGPSlines = 0
		}
		(NR > 1) {
			# clear various values
			dataOut = ""
			fieldSep = ""
			noGPSfield = 0
			noInitialValue = 0
			lineIsSkipped = 0

			# process a data-line
			for (i = 1; i <= numColumnNames; i++) {
				# skip data lines that are incomplete (i.e., have missing fields)
				# [not applicable if previously processed by extractCSVdata.sh]
				if (NF != numColumnNames) {
					invalidLinesSkipped++
					lineIsSkipped = 1
					break  # ignore this entire data-line entry
				}

				currVal[i] = trim($i)

				# if required, skip entries that have no GPS (satellite count) data
				if ((simplifyData == 1) &&
					 (columnName[i] == "sats") && (length(currVal[i]) == 0)) {
					noGPSfield = 1
					lineIsSkipped = 1
					continue  # do not break ... need to pick up missing field values
				}

				# since there can be NO empty fields in a VBO, it is necessary to
				# skip any initial entries that have empty fields -- i.e., if there
				# is no previous value for this field and there is no current value
				# for this field, this data/line entry is ignored because the VBO
				# file cannot have empty field-values
				if ((length(prevVal[i]) == 0) && (length(currVal[i]) == 0)) {
					noInitialValue = 1
					lineIsSkipped = 1
					continue  # do not break ... need to pick up missing field values
				}

				# if the previous value for this field is not yet set and there is a
				# current value for this field, set the previous value to this
				# current value (i.e., first prevVal is first-encountered currVal)
				if ((length(prevVal[i]) == 0) && (length(currVal[i]) != 0)) {
					prevVal[i] = currVal[i]
				}

				# a VBO entry has no empty fields, so use a "copy forward" strategy:
				# if there is NO current value for this field and the previous value
				# for this field is set, set the current value for this field to the
				# previous value (i.e., fill empty fields with prevVal copy forward)
				# [yeah, this could be an "else if" for improved efficiency]
				if ((length(currVal[i]) == 0) && (length(prevVal[i]) != 0)) {
					currVal[i] = prevVal[i]
				}

				# perform required field-value conversions and computations
				if (columnName[i] == "time") {
					# convert the RC UTC date/time (with msec) value to a time-only
					# value in HHMMSS.msec format with time-zone offset
					uDate = \
						substr(currVal[i], 1, (length(currVal[i]) - 3)) + secsOffset
					msecs = substr(currVal[i], (length(currVal[i]) - 2))
					secs = uDate % 60
					mins = int(uDate / 60) % 60
					hrs = int(uDate / 3600) % 24
					currVal[i] = sprintf("%02d%02d%02d.%s", hrs, mins, secs, msecs)

					# the first prevVal value must be a computed value
					if (NR == 2) { prevVal[i] = currVal[i] }

					# append the time value to the collection of data fields
					dataOut = \
						(dataOut)sprintf("%s"theFieldFormat[i], fieldSep, currVal[i])

					# warn if time crosses midnight because RaceLogic likely fails
					# NOTE: this assumes that the UTC is in proper/ascending
					#       time-order in the file ... done via extractCSVdata.sh
					if (currVal[i] < prevVal[i]) {
						system("echo >&2 ; echo WARNING: the time \\("prevVal[i]" to "currVal[i]"\\) crossed midnight so Circuit Tools may fail -- specify another time-zone offset to fix >&2")
					}
				}
				else if (columnName[i] == "lat") {
					prevVal[i] = currVal[i]

					# determine the current latitude in radians
					currRadLat = currVal[i] * deg2radMult

					# convert the latitude from decimal degrees to minutes format
					currMinLat = currVal[i] * 60.0
					currVal[i] = currMinLat
				}
				else if (columnName[i] == "long") {
					prevVal[i] = currVal[i]

					# determine the current longitude in radians
					currRadLong = currVal[i] * deg2radMult

					# convert the longitude from decimal degrees to minutes format
					currMinLong = currVal[i] * -60.0
					currVal[i] = currMinLong
				}
				else if (columnName[i] == "heading") {
					# initialize the previous lat/long in radians values
					if (prevRadLat == -1.0) { prevRadLat = currRadLat }
					if (prevRadLong == -1.0) { prevRadLong = currRadLong }

					# using lat/long data, compute a heading/bearing value to replace
					# the placeholder "SessionTime" data in the heading/bearing field
					if ((currRadLat != prevRadLat) && (currRadLong != prevRadLong)) {
						# there is a change in both lat/long coordinates so there is a
						# change in the heading/bearing, so calculate the new heading
						headingRad = \
							atan2(sin(currRadLong - prevRadLong) * cos(currRadLat), \
									(cos(prevRadLat) * sin(currRadLat)) - \
										(sin(prevRadLat) * cos(currRadLat) * \
											cos(currRadLong - prevRadLong)))

						# remember the previous lat/long values
						prevRadLat = currRadLat
						prevRadLong = currRadLong
					}

					headingDeg = ((headingRad * rad2degMult) + 360) % 360

					if (doFieldDS[i] == 1) {
						# initialize the previous data-smoothed heading value
						if (prevDSheadingDeg == -1) { prevDSheadingDeg = headingDeg }

						# apply exponential data smoothing (DS) to the heading value
						headingDeg = \
							(alphaDS * headingDeg) + ((1 - alphaDS) * prevDSheadingDeg)
						prevDSheadingDeg = headingDeg
					}

					# append the heading value to the collection of data fields
					dataOut = \
						(dataOut)sprintf("%s"theFieldFormat[i], fieldSep, headingDeg)
				}

				# for the non-transformed/non-computed fields, set the prevVal
				if ((columnName[i] != "lat") && (columnName[i] != "long")) {
					prevVal[i] = currVal[i]
				}

				# accumulate the space-separated data fields
				if (i == 2) { fieldSep = " " }

				# do any applicable data-smoothing
				# NOTE: the time and heading fields are never smoothed
				if ((columnName[i] != "time") && (columnName[i] != "heading")) {
					if (doFieldDS[i] == 1) {
						# initialize the previous data-smoothed heading value
						if (length(prevDSval[i]) == 0) { prevDSval[i] = currVal[i] }

						# apply exponential data smoothing (DS) to the value
						currVal[i] = \
								(alphaDS * currVal[i]) + ((1 - alphaDS) * prevDSval[i])
						prevDSval[i] = currVal[i]
					}

					# append the field value to the collection of data fields
					dataOut = \
						(dataOut)sprintf("%s"theFieldFormat[i], fieldSep, currVal[i])
				}
			}

			# update counts
			if (noGPSfield == 1) { nonGPSlines++ }
			if (noInitialValue == 1) { initialLinesSkipped++ }

			# if it is not to be skipped, output the data line
			if (lineIsSkipped != 1) { printf("%s\n", dataOut) }
		}
		END {
			# output any non-zero counts information
			if (invalidLinesSkipped > 0) {
				system("echo >&2 ; echo NOTE: ignored "sprintf("%\047d", invalidLinesSkipped)" CSV line\\(s\\) that had an insufficient number of fields >&2")
			}

			if (nonGPSlines > 0) {
				system("echo >&2 ; echo NOTE: to simplify, ignored "sprintf("%\047d", nonGPSlines)" CSV line\\(s\\) that had no GPS coordinates \\(i.e., were data only\\) >&2")
			}

			if (initialLinesSkipped > 0) {
				system("echo >&2 ; echo NOTE: when filling empty fields, ignored "sprintf("%\047d", initialLinesSkipped)" initial CSV line\\(s\\) with empty field\\(s\\) and no previous value >&2")
			}
		}' >> "$VBO_FILE_PATHNAME"

echo "\nCreated the VBO file:\n$VBO_FILE_PATHNAME"
echo "\nCleaning up ..."

# if required, convert the file's EOLs to DOS/Windows-style EOLs
if test "$USE_DOS_EOL" = '1'
then
	/usr/bin/awk \
		'{ printf("%s\r\n", $0) }' < "$VBO_FILE_PATHNAME" > "$WORK_FILE_PATHNAME"
	/bin/mv "$WORK_FILE_PATHNAME" "$VBO_FILE_PATHNAME"
else
	/bin/rm -f "$WORK_FILE_PATHNAME"
fi

echo "\nProcessing completed."

# reset the IFS (in case this script gets "inlined" into another script)
IFS=$IFS_SAVE
