#!/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=1
# 1 = Enable generating gophermaps and directories
GOPHERENABLE=1

# 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/bin/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=0

#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/~freet/currconv/$CODE\ttilde.club\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"
