#!/bin/bash

# Currency conversion rates list generator.
# Uses data from units_cur and ISO currency code table to format currency rates.
#
# By The Free Thinker, 2020.
# V. 1
# TODO (maybe): Generate currency lists for multiple base currencies.

# 1 = Enable generating rates list
LISTENABLE=0
# 1 = Enable generating gophermaps and directories
GOPHERENABLE=0

# Files needed for Gopher mode:
# menuhead.txt - prepended to currency menu gophermap
# gopherconvert.sh - Script that is sourced by gophermap scripts in currency directory to perform
#                    conversion and format output. Called with parameters for currency conversion.
#                     - Script location is specified below.
# [CUR]/head.txt - File in each currency directory with a custom header prepended to currency output.
#                  Generated by script to state currency name and code, if not already existant.
#

# TODO - Override above with command-line options?

#Path to gopherconvert.sh script:
GOPHERCONVSCRIPT="/home/freet/scripts/currconv/gopherconvert.sh"

# URL for fetching currency name file in [code],[name] format:
CURRENCYNAMEURL="https://raw.githubusercontent.com/umpirsky/currency-list/master/data/en_AU/currency.csv"
UPDATENAMES=1

#Number of decimal places that rate change is shown to:
CHANGESCALE=6

# Set to "0" to be quiet:
VERBOSITY=1

#Custom Currency Array (for currencies without official ISO 4217 code)
#Format is: '[code],"[name]",[ID]\n'
# [code] is a 3-letter code to stand in for an official ISO 4217 currency code.
# [name] is the display name of the currency, quote to include spaces, can't contain ",".
# [ID] is the identifier used in currency.units.
declare -a CUSTOMCURR=(\
'XBT,"Bitcoin (BTC/XBT)",bitcoin\n'\
)
#############################################################
#################### END OF CONFIGURATION ###################
#############################################################

#CURRRATE Arrays:
declare -a CURRRATE     #Conversion rate        eg. "0.40810863746164"
declare -a CURRRATEID   #Currency identifier    eg. "tongapa'anga"
declare -a CURRRATECODE #Base currency          eg. "euro"

#CURRID Arrays:
declare -a CURRID       #Currency identifier    eg. "tongapa'anga"
declare -a CURRIDCODE   #ISO 4217 currency code eg. "TOP"

OLDIFS=$IFS

### Process command-line options (currently just for convert option):
if [ "$1" == "convert" ]
then
CONVERT=1
LISTENABLE=0 # Disable currency list output whenever doing a conversion
GOPHERENABLE=0 # Also disable Gopher stuff
CONVAMOUNT="`expr "$2" : '\([0-9]*\|[0-9]*\.[0-9]*\)[a-zA-Z]*'`"
if [ $? -gt 0 ]
then
 echo -e "ERROR: Invalid Input Amount\n\
Usage is: currencyconv.sh [convert [amount][input 3-letter currency code] [output 3-letter currency code] ]"
 exit 1
fi
CONVINCODE="`echo ${2:(-3)} | tr '[:lower:]' '[:upper:]'`"
CONVOUTCODE="`echo $3 | tr '[:lower:]' '[:upper:]'`"
echo -e "From: $CONVINCODE\nTo: $CONVOUTCODE"

elif [ "$1" ]
then
echo -e "ERROR: Option not recognised.\n\
Usage is: currencyconv.sh [convert [amount][input 3-letter currency code] [output 3-letter currency code] ]"
exit 1
else
CONVERT=0
fi


### Download latest currency names file if it has changed:
####TODO: Handle 404, etc., errors.
if [ $UPDATENAMES -gt 0 ]
then
[ $VERBOSITY -gt 0 ] && echo "Checking for updated currency names..."
if [ ! -e currency.names.etag ]
then
 [ $VERBOSITY -gt 0 ] && echo "Can not find etag file - First run?"
 wget -q -S -O currency.names $CURRENCYNAMEURL 2>&1 | sed -n 's/ *ETag: \(.*\)$/\1/p' > currency.names.etag
else
 ETAG="`cat currency.names.etag`"
 wget -v -S -O currency.names.new --header="If-None-Match: $ETAG" $CURRENCYNAMEURL 2>&1 | sed -n 's/ *ETag: \(.*\)$/\1/p' > currency.names.etag
 if [ -s currency.names.new ]
 then
  [ $VERBOSITY -gt 0 ] && echo "Currency names updated."
  mv currency.names.new currency.names
 fi
fi
fi

IFS=","
###Add custom currencies to start of CURRID arrays:
for (( COUNT = 0 ; COUNT < ${#CUSTOMCURR[*]} ; COUNT++ ))
do
CURRID[COUNT]="`echo -n -e "${CUSTOMCURR[COUNT]}" | cut -d "," -f 3`"
CURRIDCODE[COUNT]="`echo -n -e "${CUSTOMCURR[COUNT]}" | cut -d "," -f 1`"
done

IFS=" "
### Read currency codes from Units rates file
grep -E '^[A-Z]{3} +[a-zA-Z$]+$' currency.units > currency.units.tmp
exec 3<> currency.units.tmp
while read -r CODE ID <&3
do
CURRID[COUNT]="$ID"
CURRIDCODE[COUNT]="$CODE"
(( COUNT++ ))
done
exec 3>&-

### Read currency rates from Units rates file
COUNT=0
grep -E '^[a-z]+ +[0-9]' currency.units > currency.units.tmp
exec 3<> currency.units.tmp
while read -r ID RATE CODE <&3
do
#Convert rate from scientific notation to suit bc (eg. "e-05" = "*10^05")
# Copied from: https://stackoverflow.com/questions/12882611/how-to-get-bc-to-handle-numbers-in-scientific-aka-exponential-notation
RATE=${RATE/e/*10^}; RATE=${RATE/^+/^} #Don't really know if the e+[num] form will crop up, playing safe.

if [[ "$RATE" == 1'|'* ]]; then
 RATE="`expr "$RATE" : '.*|\([0-9.]*\)'`" #Strip "1|" bit from rate string
 RATE="`echo "scale=15; 1 / $RATE" | bc`" #Divide by one using bc (supports floating point calculations)
fi
CURRRATEID[COUNT]="$ID"
CURRRATE[COUNT]="$RATE"
CURRRATECODE[COUNT]="$CODE"
(( COUNT++ ))
done
exec 3>&-
rm currency.units.tmp

if [ $LISTENABLE -gt 0 ]
then
# Output Units rates file update time
echo -n "Currency data last updated" >  currencyrates.new
grep -E -o ' on [0-9]{4}-[0-9]{2}-[0-9]{2}$' currency.units >> currencyrates.new
echo >> currencyrates.new
elif [ $CONVERT -gt 0 ]
then
RATESUPDATEDATE="`grep -E -o ' on [0-9]{4}-[0-9]{2}-[0-9]{2}$' currency.units`"
fi

if [ $GOPHERENABLE -gt 0 ]
then
[ -f menuhead.txt ] && cp menuhead.txt gophermap.new || echo "menuhead.txt not found"
# Output Units rates file update time
echo -n "    Currency data last updated" >>  gophermap.new
grep -E -o ' on [0-9]{4}-[0-9]{2}-[0-9]{2}$' currency.units >> gophermap.new
[ -d gopherconvert_links ] && rm -f gopherconvert_links/* || mkdir gopherconvert_links

#Create links to the binaries used by the gopherconvert.sh Bash script that runs in restricted mode
declare -a BIN=( expr tr cat units ) #All commands used that aren't Bash built-ins

for (( i = 0 ; i < ${#BIN[*]} ; i++ ))
do
 LOCATION="`whereis -b ${BIN[i]}`"
 LEN=$[ ${#bin[i]} + 2 ]
 ln -s "`expr \"$LOCATION\" : \".* \(/.*bin/${BIN[i]}\)\"`" gopherconvert_links/
done

rm -f customcurr.csv
for (( i = 0 ; i < ${#CUSTOMCURR[*]} ; i++ ))
do
 echo "${CUSTOMCURR[i]}" >> customcurr.csv
done

# ln -s "$GOPHERCONVSCRIPT" gopherconvert_links/gophermap # -- Gophernicus can't figure links out
fi


### Read currency names and produce output
#### TODO: Optimise to avoid checking IDs that have already been matched.
[ $VERBOSITY -gt 0 ] && echo -n "Processed: "
LINE=1
IFS=","
echo -n -e "${CUSTOMCURR[*]}" | sort currency.names - | while read -r CODE NAME ID
do
NAME="`echo "$NAME" | tr -d '"'`" # Strip any quotes from around currency name

# Find matching currency code in Units data, to reveal the index number of the ID string
IDCOUNT=0
while [ "${CURRIDCODE[IDCOUNT]}" != "" ] && [ "${CURRIDCODE[IDCOUNT]}" != "$CODE" ] ; do
 (( IDCOUNT++ ))
done

if [ "${CURRIDCODE[IDCOUNT]}" != "" ]
then
 LINE=$[ $LINE + 2 ] #Increment count of lines in currencyrates.new

 # Find currency rate associated with extracted ID string
 RATECOUNT=0
 while [ "${CURRRATEID[RATECOUNT]}" != "${CURRID[IDCOUNT]}" ] && [ $RATECOUNT -lt ${#CURRRATEID[*]} ]
 do
  (( RATECOUNT++ ))
 done

 [ $VERBOSITY -gt 0 ] && echo -n " ${CURRIDCODE[IDCOUNT]} "

 ### Convert currency
 if [ $CONVERT -gt 0 ]
 then
  #Grab the needed data as it passes by:
  if [ "$CODE" == "$CONVINCODE" ]
  then
   CONVINNAME="$NAME"
   CONVINRATE="${CURRRATE[RATECOUNT]}"
   CONVINBASE="${CURRRATECODE[RATECOUNT]}"
   [[ "$CONVINBASE" == "US\$"* ]] && CONVINBASE=USD
  elif [ "$CODE" == "$CONVOUTCODE" ]
  then
   CONVOUTNAME="$NAME"
   CONVOUTRATE="${CURRRATE[RATECOUNT]}"
   CONVOUTBASE="${CURRRATECODE[RATECOUNT]}"
   [[ "$CONVOUTBASE" == "US\$"* ]] && CONVOUTBASE=USD
  fi
  # Once we've got everything, time for some output:
  if [ $CONVINNAME ] && [ $CONVOUTNAME ]
  then
   if [ "$CONVINBASE" != "$CONVOUTBASE" ] && [ "$CONVINBASE" ] && [ "$CONVOUTBASE" ]
   then
    echo -e "\n\nSorry, the currency data uses different base currencies for the currencies that you want to convert \
between, and converting between different base currencies is just too confusing for this poor little script.\n\n\
I'm sure that GNU Units would be happy to help, if you have it handy..." #'
   elif [ "$CONVINCODE" == "$CONVOUTBASE" ]
    then echo -n -e "\n\n  Result: "; printf "%.4f\n" "`echo "scale=15; $CONVAMOUNT / $CONVOUTRATE" | bc`"
   elif [ "$CONVOUTCODE" == "$CONVINBASE" ]
    then echo -n -e "\n\n  Result: "; printf "%.4f\n" "`echo "scale=15; $CONVAMOUNT * $CONVINRATE" | bc`"
   else
    echo -n -e "\n\n  Result: "; printf "%.4f\n" "`echo 'scale=15; ('$CONVAMOUNT' * '$CONVINRATE') / '$CONVOUTRATE | bc`"
   fi
  echo -e "  ------------------------------------------------------------------\n\
Input currency name:  $CONVINNAME\tRATE: $CONVINRATE $CONVINBASE\n\
Output currency name: $CONVOUTNAME\tRATE: $CONVOUTRATE $CONVOUTBASE\n\
Currency rates last updated$RATESUPDATEDATE"
  exit 0
  fi
 fi

 ###Output rates table in alphabetical order of currency codes
 ### Write currency code, name from currency.names, and rate, to currencyrates file:
 if [ $LISTENABLE -gt 0 ]
 then
  CHANGESTRING=

  #Rise/Fall Indicator
  #Output rate change since last run, if currency list (and base currency) not changed:
  if [ -r currencyrates.txt ] && expr "`sed -n "$LINE p" currencyrates.txt`" : "${CURRIDCODE[IDCOUNT]}" > /dev/null #Check currency codes match
  then
   OLDRATE="`sed -n "$[ $LINE + 1 ] p" currencyrates.txt`" # Grab line with currency rate
   if expr "$OLDRATE" : '.*'"${CURRRATECODE[RATECOUNT]}"'.*' > /dev/null # Check that base currencies match
   then
    OLDRATE="`expr "$OLDRATE" : ' *\([0-9.^+*-]*\)'`" #Get old currency rate
    if [ $OLDRATE ] #Base currency won't have a rate number, so skip it.
    then
       # echo "${CURRRATE[RATECOUNT]} - $OLDRATE"
     CHANGE="`printf "%.${CHANGESCALE}f" \`echo "scale=15; ${CURRRATE[RATECOUNT]} - $OLDRATE" | bc\``" #Calculate change in currency rate

     if [ "$CHANGE" == "`printf "%.${CHANGESCALE}f"`" ]
     then #If currency is steady, mark with "-"
      CHANGESTRING='  ( - )'
     else
      #Otherwise mark "^" or "v" depending on whether number is marked negative:
      [[ "$CHANGE" != '-'* ]] && CHANGESTRING='  (^ '$CHANGE')' || CHANGESTRING='  (v '$CHANGE')'
     fi
    fi
   fi
  fi
 #Output line:
 echo -e "${CURRIDCODE[IDCOUNT]} - $NAME:\n       ${CURRRATE[RATECOUNT]} ${CURRRATECODE[RATECOUNT]}$CHANGESTRING" >> currencyrates.new
  # echo -n "Line $LINE: " ; sed -n "$LINE p" currencyrates.txt
 fi

 ###Generate Gophermap and Gopher currency directories
 if [ $GOPHERENABLE -gt 0 ]
 then
  # Create and populate currency directory if it's not already there:
  if [ ! -d "$CODE" ]
  then
   mkdir "$CODE"
   echo -e '#!/bin/bash\nPATH= . '"$GOPHERCONVSCRIPT" > "$CODE/gophermap"
   #echo -e '#!/bin/bash\n. ../gopherconvert.sh $QUERY_STRING' > "$CODE/gophermap" #For old/alternative gopherconvert.sh
   chmod a+rx,go-w "$CODE/gophermap"
   echo -e "   ---   Converted to $NAME ($CODE)   ---   \n" > "$CODE/head.txt"
  fi

  # Copy fresh links to executables used by gopherconvert.sh so that restricted Bash can find them:
  cp -d gopherconvert_links/* "$CODE/"

  # Add currency to menu gophermap:
  echo -e "7$CODE - $NAME:\t$CODE\n       ${CURRRATE[RATECOUNT]} ${CURRRATECODE[RATECOUNT]}$CHANGESTRING" >> gophermap.new
 fi

fi
done

[ $VERBOSITY -gt 0 ] && echo
[ $LISTENABLE -gt 0 ] && mv currencyrates.new currencyrates.txt
[ $GOPHERENABLE -gt 0 ] && mv gophermap.new gophermap
IFS="$OLDIFS"