#!/bin/bash
###########################################################################
# Authors: Kaveh Afshar (kaveh.afshar@ec.gc.ca)
#          Thomas Gibson (thomas.gibson@ec.gc.ca)
# Date:    March 11, 2011
# Version: 1.1
#
# Purpose: To parse the Hurricange GUI FXCN bulletin and create a mappable
#          output product for CHC clients.
# 
# Detail:  This script is designed to take HGUI's FXCN bulletin as input and
#          generate Shape file representations of the hurricane for CHC
#          clients.
# 
# Usage:   createMappableProduct <FXCN_bulletin>
# 
# Return:  0 will be returned on success.
#          1 will be returned on error.
#
# Revision History:
#   Mar. 11/11  v0.1  KA	-Original version.
#   Mar. 16/11  v0.5  TG	-Added routines to create shape files.
#   Mar. 16/11  v0.6  KA	-Checking for shapelib prior to execution.
#   Mar. 16/11  v0.7  KA	-Improved extractTimeZone.
#   				-Added extractIssueTime Routine.
#   Apr. 18.11  v0.8  TG        -Added wind radii, track uncertainty
#   May  18.11  v0.9  TG        -Removed quotes in output column
#   Jun. 01/11  v1.0  KA        -Fixed dependency check function.
#   Jun. 01/11  v1.1  TG        -Make header parsing more robust
#   Jun. 02/11  v1.2  TG        -Allow wind radii to be optional
#
###########################################################################
# Debugging level
##set -x

#-------------------------------------------------
# Variables Used
#-------------------------------------------------
PURPOSE="\n\nPurpose: This script is designed to take HGUI's FXCN bulletin as input and"
PURPOSE="${PURPOSE}\n\t generate Shape file representations of the hurricane for CHC clients."
FXCN_FNAME=             # The name of the prospective input file
SECTION2_TABLE=		# The Table in Section 2 of the FXCN bulletin
SECTIONC_TABLE=         # The Table in Section C of the FXCN bulletin
HEADER_TZ=		# The Time Zone as indicated on line ~3 of the FXCN
HEADER_YEAR=		# The Year (YYYY) indicated on line ~4 of the FXCN
HEADER_MONTH=		# The Month (MM) indicated on line ~4 of the FXCN
HURRICANE_NAME=		# The Hurricane name as indicated in the FXCN
HURRICANE_FILEID=	# The ID used in generated filenames
ISSUE_TIME=		# The FXCN ISSUE TIME


#-------------------------------------------------
# This function is called when we need to show
# the user how to use this script
#-------------------------------------------------
printUsage ()
{
  echo -e "${PURPOSE}"
  echo -e "\nUsage:  `basename $0` [ -help ]"
  echo -e "\t`basename $0` <FXCN bulletin file>\n"
  echo -e "\t-help\t\t     Shows this usage screen."
  echo -e "\t<FXCN bulletin file> Path to the FXCN bulletin.\n\n" 
}


#-------------------------------------------------
# This function checks the number of aruguments 
# passed to this script and calls "printUsage"
# if required.
# 
# This function ensures that there's exactly 
# one argument and that the argument is either 
# "help" or a value representing an input file.
# 
# The name of the prospective FXCN input file
# is placed in the global variable FXCN_FNAME.
#-------------------------------------------------
checkNumOfGivenArgs ()
{
local numOfArgs=$1
local firstArg=$2
if [ ${numOfArgs} -ne 1 ]
then
  printUsage
  exit 1
else 
  case "${firstArg}" in
  -help | -HELP | -h | -H | --help)
    printUsage
    exit 0
    ;;
  *)	
    FXCN_FNAME="${firstArg}"
    ;;
  esac
fi 
}


#-------------------------------------------------
# This funciton checks the given input to 
# ensure it's a valid file.
# 
# Validity is defined here by the file being 
# redable and of size greater than 0.
# 
#-------------------------------------------------
checkInputValidity ()
{
# Ensure input file exists
#
if [ ! -r "${FXCN_FNAME}" ]
then
  echo -e "\nThe FXCN file: \"${FXCN_FNAME}\" could not be found!\n"
  exit 1
fi

# Ensure file is non-empty?
#
if [ `stat -c "%s" "${FXCN_FNAME}"` -eq 0 ]
then
  echo -e "\nThe FXCN file: \"${FXCN_FNAME}\" is empty!\n"
  exit 1
fi
}


#-------------------------------------------------
# This funciton checks the given file to ensure  
# it's a valid FXCN.
# 
# Validity is defined here by the file possessing 
# the string "FXCN" on the first line.
# ------------------------------------------------
checkFXCNValidity ()
{
# Look for "FXCN" on the first line
# 
searchResult=`head -1 "${FXCN_FNAME}" | grep FXCN` 
local RC=$?
if [ ${RC} != 0 ]
then
  echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to be a valid FXCN file!"
  exit 1
fi
}


#-------------------------------------------------
# This funciton extracts the table in section 2
# of the FXCN.
#
# If the table in Section 2 exists, it is placed
# in the global variable SECTION2_TABLE.
# ------------------------------------------------
extractTrackTable ()
{

# This is a sampe of the table in Section 2
# of the FXCN:
#
# 2. FORECAST POSITION, CENTRAL PRESSURE AND STRENGTH
#
# DATE     TIME     LAT    LON   MSLP  MAX WIND
#          AST                    MB  KTS  KMH
# MAY 28  2.00 PM  21.8N  63.0W   987   65  120
# MAY 29  2.00 AM  27.6N  66.6W   987   65  120

local OLDIFS=$IFS
IFS=$'\n'
SECTION2_TABLE=( $(grep "^[A-Z][A-Z][A-Z] [0-9][0-9]" ${FXCN_FNAME}) )
IFS=$OLDIFS
 

# Ensure the table is present
# 
if [ -z "${SECTION2_TABLE}" ] 
then 
  echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a table in section 2!"
  exit 1
fi
}

#-------------------------------------------------
# This funciton extracts the table in section C
# of the FXCN.
#
# If the table in Section C exists, it is placed
# in the global variable SECTIONC_TABLE.
# ------------------------------------------------
extractWindTable ()
{

# This is a sample of the table in Section C
# of the FXCN:
#

#C. PREDICTED WIND RADII (NM) – SAMPLE HERE:
#
#TIME          GALES           STORMS            HURRICANE
#         NE  SE  SW  NW    NE  SE  SW  NW    NE  SE  SW  NW
#29/00Z  210 150 100 120   120  90  30  60    70  50  10  30
#29/06Z  210 150 100 120   120  90  30  60     0   0   0   0


local OLDIFS=$IFS
IFS=$'\n'
SECTIONC_TABLE=( $(grep "^[0-9][0-9]/[0-9][0-9]Z" ${FXCN_FNAME}) )
IFS=$OLDIFS

}


#-------------------------------------------------
# This funciton creates a shapefile containing
# of a Hurricane track point based on the content
# of a FXCN bulletin.
#
# It take a line array of the table in the second
# section of a FXCN bulletn as an argument
# ------------------------------------------------
createShpPoints ()
{
declare -a trackPtTable=("${!1}")
declare -a trackWindTable=("${!2}")

local zone=$TZ
local stormType=$(getStormType)
local tau lat lng pres knt tcdvlp
local errCt tp0 tp0Z

# Set Timezone to match bulletin
export TZ=$HEADER_TZ

# Create shapefile to hold points
shpcreate $HURRICANE_FILEID.pts.shp point
# Create database to hold track point metadata
dbfcreate $HURRICANE_FILEID.pts.dbf -s STORMNAME 20 -s STORMTYPE 4 -s ADVDATE 12 -s BASIN 4 -s LAT 10 -s LON 10 -s VALIDTIME 20 -s TAU 4 -s MAXWIND 4 -s MSLP 6 -s TCDVLP 4 -s DATELBL 20 -s TIMEZONE 4 -s ERRCT 5 -s R34NE 4 -s R34SE 4  -s R34SW 4 -s R34NW 4 -s R48NE 4 -s R48SE 4 -s R48SW 4 -s R48NW 4 -s R64NE 4 -s R64SE 4 -s R64SW 4 -s R64NW 4


# Get Valid Time for First Forecast Point
tp0=`echo ${trackPtTable[0]}|awk '{for(i=1;i<5;i++)printf "%s ", $i}'|tr '.' ':';echo $HEADER_YEAR;echo $TZ`
tp0Z=$(date -d "$tp0" +%s)

# Iterator through point; add point to shapefile; add metadata to db
for (( i=0 ; i < ${#trackPtTable[@]} ; i++ ))
do
# Time of current point
TP=`echo ${trackPtTable[$i]}|awk '{for(i=1;i<5;i++)printf "%s ", $i}'|tr '.' ':';echo $HEADER_YEAR;echo $TZ`

# Determine hour offset for current forecast point
tau=$((( $(date -d "$TP" +%s) - $tp0Z)/3600))

# Determine the track uncertainty
errCt=$(getTrackUncertainty $tau)

# Get Wind radii for this forecast point
local OLDIFS=$IFS
IFS=' '
wndRadii=( ${trackWindTable[$i]} )
IFS=$OLDIFS


# Extract relevant attribute from row
lat=`echo ${trackPtTable[$i]}|awk '{ if (match($5,"N")) printf "%.2f", $5; else printf "-%.2f", $5;}'`
lng=`echo ${trackPtTable[$i]}|awk '{ if (match($6,"W")) printf "-%.2f", $6; else printf "%.2f", $6;}'`
pres=`echo ${trackPtTable[$i]}|awk '{printf "%.1f", $7}'`
knt=`echo ${trackPtTable[$i]}|awk '{printf "%d", $8}'`
tcdvlp=`echo ${trackPtTable[$i]}|awk '{printf "%s", $10}'`

# Ensure tcdvlp is present; further classify if necessary
# 
if [ -z "${tcdvlp}" ]
    tcdvlp="PT";
then
    if [ $knt -ge 65 ]
    then
        tcdvlp="HU"
    elif [ $knt -ge 35 ]
    then
        tcdvlp="TS"
    else
        tcdvlp="TD"
    fi
fi

shpadd $HURRICANE_FILEID.pts.shp $lng $lat
datelbl="`date -d "$TP" +"%-l:%M %p %a"`"
if [ -z "${SECTIONC_TABLE}" ]
then
dbfadd $HURRICANE_FILEID.pts.dbf $HURRICANE_NAME $stormType $ISSUE_TIME  al $lat $lng `date -u -d "$TP" +"%d/%H%M"` $tau $knt $pres $tcdvlp "$datelbl" `date -d "$TP" +"%Z"` $errCt NA NA NA NA NA NA NA NA NA NA NA NA
else
dbfadd $HURRICANE_FILEID.pts.dbf $HURRICANE_NAME $stormType $ISSUE_TIME  al $lat $lng `date -u -d "$TP" +"%d/%H%M"` $tau $knt $pres $tcdvlp "$datelbl" `date -d "$TP" +"%Z"` $errCt ${wndRadii[@]:1}
fi

done

# Restore TZ
export TZ=$zone

}

#-------------------------------------------------
# This funciton creates a shapefile containing
# of a Hurricane track line based on the content
# of a FXCN bulletin.
#
# It take a line array of the table in the second
# section of a FXCN bulletn as an argument
# ---------------------------
createShpArc ()
{
declare -a trackPtTable=("${!1}")
local pnts=""
local lat lng

# Create Attribute file
dbfcreate $HURRICANE_FILEID.lin.dbf -s STORMNAME 20 -s STORMTYPE 20 -s BASIN 20
dbfadd $HURRICANE_FILEID.lin.dbf $HURRICANE_NAME $(getStormType) al


# Create shapefile to hold track
shpcreate $HURRICANE_FILEID.lin.shp arc


# Iterator through points to create list
for (( i=0 ; i < ${#trackPtTable[@]} ; i++ ))
do
  lat=`echo ${trackPtTable[$i]}|awk '{ if (match($5,"N")) printf "%f", $5; else printf "-%f", $5;}'`
  lng=`echo ${trackPtTable[$i]}|awk '{ if (match($6,"W")) printf "-%f", $6; else printf "%f", $6;}'`

  pnts="$pnts $lng $lat"
done

# Add track to shapefile
shpadd $HURRICANE_FILEID.lin.shp $pnts

}

#-------------------------------------------------
# This funciton ensures that the required libraries
# for creating output exist.
# ------------------------------------------------
checkForLibraryPresence ()
{
# Name of the shape library to check for
#
local librayName="shapelib"
searchResult=`dpkg -l |grep ${librayName} | grep ii`
local RC=$?
if [ ${RC} != 0 ]
then
  echo -e "\nThe required library: \""${librayName}"\" does not seem to be installed on this server!"
  exit 1
fi

# Name of the shape library to check for
#

librayName="zip"
searchResult=`dpkg -l |grep ${librayName} | grep ii`
local RC=$?
if [ ${RC} != 0 ]
then
  echo -e "\nThe required library: \""${librayName}"\" does not seem to be installed on this server!"
  exit 1
fi
}


#-------------------------------------------------
# This funciton extracts the timezone from the 
# header block of the FXCN. The value is checked 
# to make sure that it's 3 characters long.
#
# The extracted timezone is placed in the global
# variable HEADER_TZ.
# ------------------------------------------------
extractTimeZone ()
{
local searchString="ENVIRONMENT CANADA AT "

# Parsing the line in the FXCN:
# HURRICANE CENTRE OF ENVIRONMENT CANADA AT XX.XX AM/PM ADT/NDT XXXDAY XX MONTH YYYY.

# Extract the timezone
#
HEADER_TZ=`head -n5 "${FXCN_FNAME}" | grep "${searchString}" | awk -F "${searchString}" '{print $2}' | awk '{print $3}'`

# Ensure something resembling the timezone was extracted
#
if [ -z "${HEADER_TZ}" ] || [ `expr length "${HEADER_TZ}"` -ne 3 ]
then
   echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a valid timezone on line 3!"
   exit 1
fi
}

#-------------------------------------------------
# This funciton returns the track uncertainty
# given tau
# ------------------------------------------------
getTrackUncertainty ()
{

uncertaintyLUT=( 0 28 44 59 78 117 206 )
foreCstTime=( 0 12 24 36 48 72 120 )

if [ $1 -le ${foreCstTime[0]} ]
then
  echo ${uncertaintyLUT[0]}
elif [ $1 -ge ${foreCstTime[${#foreCstTime[@]}-1]} ]
then
  echo ${uncertaintyLUT[${#uncertaintyLUT[@]}-1]}
else
  for (( i=1 ; i < ${#uncertaintyLUT[@]} ; i++ ))
  do
    if [ $1 -le ${foreCstTime[i]} ]
    then
	echo ${uncertaintyLUT[i-1]} $1 ${foreCstTime[i-1]} ${foreCstTime[i]} ${uncertaintyLUT[i]} ${uncertaintyLUT[i-1]} | awk '{printf "%.1f\n", $1 + ( ( $2 - $3 ) / ( $4 - $3 ) / ( 1.0 / ( $5 - $6 ) ) )}'
        break
    fi
  done
fi

}
#-------------------------------------------------
# This funciton returns the stormtype abbrev.
# based on the valus in the FXCN
# ------------------------------------------------
getStormType ()
{
declare -a STYPE
STYPE=([TD]="TROPICAL DEPRESSION" [TS]="TROPICAL STORM" [ST]="SUBTROPICAL STORM" [PT]="POST-TROPICAL STORM" [HU]="HURRICANE")
local myresult="TD"

# String to look for within the FXCN
#
local lookForString="WAS LOCATED"

# Extract the nameline
#
NAMELINE=`grep -i "${lookForString}" "${FXCN_FNAME}"`

for element in ${!STYPE[@]}; do
  if [[ $NAMELINE == *$element* ]]
  then
  myresult=$element;
  fi
done

echo $myresult

}

#-------------------------------------------------
# This funciton extract the Year (YYYY) indicated 
# on line 4 of the FXCN. If the YEAR is followed
# by a ".", it is stripped. The extracted date
# is topically validated to ensure it resembles
# a year value.
#
# The extracted year is placed in the global
# variable HEADER_YEAR.
# ------------------------------------------------
extractYear ()
{
# Extract the year
#
HEADER_YEAR=`cat "${FXCN_FNAME}" | tr -s '\n' ' ' | cut -f 25 -d ' '`

# Ensure something was extracted
#
if [ -z "${HEADER_YEAR}" ]
then
   echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a valid year (YYYY) on line 4!"
   exit 1
fi

# Strip the trailing period if the date ends in a period
# I.e. "2011."
#
if [ `expr length "${HEADER_YEAR}"` -eq 5 ] && [ "${HEADER_YEAR:4:1}" == "." ]
then
  HEADER_YEAR="${HEADER_YEAR:0:4}"
fi

# Ensure the extracted value resembles a year value
#
if [ ${HEADER_YEAR} -lt 2000 ] || [ ${HEADER_YEAR} -gt 2100 ]
then
  echo -e "\nExtracted the year value: \"${HEADER_YEAR}\" from the given file: \"${FXCN_FNAME}\"."
  echo -e "This value does not seem valid!"
  exit 1
fi
}


#-------------------------------------------------
# This funciton extract the Month indicated
# on line 4 of the FXCN. If the Month is then
# converted to its numerical equivalent and
# testted to ensure it seems realistic.
#
# The value is then placed in the global
# variable HEADER_MONTH.
# ------------------------------------------------
extractMonth ()
{
local numericMonth=

# Extract the month (second-last field)
#
HEADER_MONTH=`cat  "${FXCN_FNAME}" | tr -s '\n' ' ' | cut -f 24 -d ' '`

# Ensure something was extracted
#
if [ -z "${HEADER_MONTH}" ]
then
   echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a valid month (non-numerical) on line 4!"
   exit 1
fi

# Convert the non-numerical month to its numerical equivalent
#
numericMonth=`date -d "${HEADER_MONTH} 01, 2011" +"%m"`
HEADER_MONTH=${numericMonth}

# Ensure the extracted value resembles a month value
#
if [ ${HEADER_MONTH} -lt 0 ] || [ ${HEADER_MONTH} -gt 12 ]
then
  echo -e "\nExtracted the month value: \"${HEADER_MONTH}\" from the given file: \"${FXCN_FNAME}\"."
  echo -e "This value does not seem valid!"
  exit 1
fi
}


#-------------------------------------------------
# This funciton extracts the hurricane name from
# the FXCN.  The name is extracted from the line
# "HURRICANE <NAME> WAS LOCATED NEAR LATITUDE"
# in the FXCN.
#
# The extracted hurricane name is placed in the 
# global variable HURRICANE_NAME.
# ------------------------------------------------
extractName ()
{
# String to look for within the FXCN
#
local lookForString="WAS LOCATED"

# Extract the hurricane name
#
HURRICANE_NAME=`grep "${lookForString}" "${FXCN_FNAME}" | awk -F"WAS" '{print $1}'| awk '{print $NF}'` 

# Ensure something was extracted
#
if [ -z "${HURRICANE_NAME}" ]
then
   echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a valid Hurricane Name!"
   exit 1
fi
}

#-------------------------------------------------
# This funciton creates a file id from the
# Hurricane name to be used for generated
# files 
# ------------------------------------------------
createFileId ()
{
# Extract the hurricane name
#
HURRICANE_FILEID=$HEADER_YEAR$HEADER_MONTH${ISSUE_TIME:4:2}"_"${ISSUE_TIME:7:4}"Z_"$HURRICANE_NAME

# Ensure something was created
#
if [ -z "${HURRICANE_FILEID}" ]
then
   echo -e "\nUnable to generate a valid file Id name!"
   exit 1
fi
}


#-------------------------------------------------
# This funciton extracts the FXCN Issue Time. The
# goal is to construct the string "YYMMDD/HHHH"
# from the FXCN.  Note that the month is given
# in the FXCN non-numerically.
#
# The extracted value is placed in the global 
# variable ISSUE_TIME.
# ------------------------------------------------
extractIssueTime ()
{
local day=
local hour=
local month=
local yearAsYY=

# Extract the hour
#
local dayHour=`head -n1 "${FXCN_FNAME}" | awk '{print $NF}'`

# Ensure something was extracted
#
if [ `expr length "${dayHour}"` -ne 6 ]
then
   echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a valid day and hour (DDHHHH) on line 1!"
   exit 1
fi

# Extract day
#
day=${dayHour:0:2}

# Error-check day value
#
if [ ${day} -lt 1 ] || [ ${day} -gt 31 ]
then
  echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a valid day (DD) on line 1!"
  exit 1
fi

# Extract hour
#
hour=${dayHour:2:4}

# Error-check hour value
#
if [ ${hour} -lt 0 ] || [ ${hour} -gt 2359 ]
then
  echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a valid hour (HHHH) on line 1!"
  exit 1
fi

# Extract 2-digit year
#
yearAsYY=${HEADER_YEAR:2:2}

# Get numeric month from global variable
#
month=${HEADER_MONTH}

# Assemble Issue Time "YYMMDD/HHHH"
ISSUE_TIME="${yearAsYY}${month}${day}/${hour}"

# Final sanity check
# Expected string looks like: "110310/1800"
#
if [ `expr length "${ISSUE_TIME}"` -ne 11 ]
then
  echo -e "\nThe given file: \"${FXCN_FNAME}\" doesn't appear to have a valid issue time (YYMMDD/HHHH) on line 1!"
  exit 1
fi
}


#-------------------------------------------------
# * * * * *  EXECUTION START HERE * * * * * 
#-------------------------------------------------

# Check the number of given arguments
# 
checkNumOfGivenArgs $# "$1"

# Inform the user of what's happening
# 
echo -e "Attempting to use the file: \""${FXCN_FNAME}"\" as the source FXCN."

# Check the validity of the given input as a valid file
# 
checkInputValidity

# Check the validity of the given file as a valid FXCN
# 
checkFXCNValidity

# Extract the table in Section 2 of the FXCN 
# 
extractTrackTable

# Extract the table in Section C of the FXCN 
# 
extractWindTable

# Extract the timezone indicated on line 3 of the FXCN 
# 
extractTimeZone
echo "Extracted Timezone: $HEADER_TZ"

# Extract the Year (YYYY) indicated on line 4 of the FXCN
# 
extractYear
echo "Extracted Year: $HEADER_YEAR"

# Extract the Year (YYYY) indicated on line 4 of the FXCN
# 
extractMonth
echo "Extracted Month: $HEADER_MONTH"

# Extract the Issue Time
# 
extractIssueTime
echo "Issue Time: $ISSUE_TIME"

# Extract the hurricane name from the string 
# "HURRICANE <NAME> WAS LOCATED NEAR LATITUDE"
# in the the FXCN
# 
extractName
echo "Extracted Hurricane Name: $HURRICANE_NAME"

# Ensure required library is installed
# 
checkForLibraryPresence

createFileId

createShpArc SECTION2_TABLE[@]

createShpPoints SECTION2_TABLE[@] SECTIONC_TABLE[@]

zip -9 $HURRICANE_FILEID.zip $HURRICANE_FILEID*.sh? $HURRICANE_FILEID*.db?

# Remove intermediate files

rm -f $HURRICANE_FILEID*.sh? $HURRICANE_FILEID*.db?

# Exit the script with a successful return value
# 
exit 0













