#!/bin/bash
# $1 is contract .sol

source /etc/geth-utils/geth.conf

####################################### USAGE ############################################

_usage() {
    echo "`basename $0` [ -a | -n ] [ -e | -ne ] file.sol"
    echo "      -a  ... analysis"
    echo "      -n  ... noanalysis (default)"
    echo "      -e  ... events (default)"
    echo "      -ne ... no events"
}

####################################### PARAMETERS ############################################

if [ "$1" = "-h" -o "$1" = "--help" ]; then
   _usage
   exit 0
fi

ANALYSIS=no
if [ "$1" = -a ]; then ANALYSIS=yes; shift; fi
if [ "$1" = -n ]; then ANALYSIS=no; shift; fi

EVENTS=yes
if [ "$1" = -e ]; then EVENTS=yes; shift; fi
if [ "$1" = -ne ]; then EVENTS=no; shift; fi

if [ -z "$1" -o \! -f "$1" ]; then
   _usage
   exit 17
fi

####################################### Events ###############################################

_createEventCode () { # $1 source .sol, $2 output file
   echo "       ####### Creating Event Code for $1 in $2"
   echo "" >> $2

   echo "function ${BN}UnwatchEvents() {" >> $2
   echo "   if ( ${BN} != null && eth.getCode(${BN}.address) != \"0x\" ) {" >> $2
   echo '       keys = [];' >> $2
   echo "       for (var key in ${BN}.allEvents().requestManager.polls) keys.push(key);" >> $2
   echo '       for (i=0;i<keys.length;i++) {' >> $2
   echo "           p(\"   ${BN}.allEvents().requestManager.polls[\" + keys[i] + \"].uninstall();\");" >> $2
   echo "           ${BN}.allEvents().requestManager.polls[keys[i]].uninstall();" >> $2
   echo '        }' >> $2
   echo '    }' >> $2
   echo '}' >> $2
   # TODO: Deletes one default event at startup; What is this?

   echo "" >> $2

   echo "function ${BN}ListEvents() {" >> $2
   echo "   if ( ${BN} != null && eth.getCode(${BN}.address) != \"0x\" ) {" >> $2
   echo "           p(\"${BN}.allEvents().requestManager.polls\");" >> $2
   echo "        return ${BN}.allEvents().requestManager.polls;" >> $2
   echo '    }' >> $2
   echo '}' >> $2

   echo "" >> $2

   echo "function ${BN}WatchEvents() {" >> $2
   echo "    if ( ${BN} != null && eth.getCode(${BN}.address) != \"0x\" ) {" >> $2
   echo "                 console.log(\"${BN}WatchEvents() called:\");" >> $2
   echo "          ${BN}UnwatchEvents()" >> $2
   cat $1 | sed '1,$ s/\/\//#/g' | cut -d '#' -f 1 | egrep '\bevent\b' | ( while read EVENTLINE; do
       # set -x
       EVENTNAME="`echo $EVENTLINE | cut -d \( -f 1 | awk '{print $2}'`"
       EVENTPARS="`echo $EVENTLINE | cut -d \( -f 2 | cut -d \) -f 1`"
       # set +x
       echo "            //  Generating Eventcode for $EVENTLINE - to override define"
       echo -n "                   function ${BN}Event_$EVENTNAME("
       echo -n "$EVENTPARS" | tr ',' '\n' | awk '{printf " %s ,",$2}' | sed '1,$ s/,$//'
       echo ") {"
       echo    "                   }"
       echo "          p(\"   ${BN}.$EVENTNAME().watch( function(error,result){ ... }\");" >> $2
       echo "          ${BN}.$EVENTNAME().watch( function(error,result){ if (!error) {" >> $2
       echo "              if ( typeof ${BN}Event_$EVENTNAME == 'function' ) {" >> $2
       echo "                     ${BN}Event_$EVENTNAME (" >> $2
       echo "$EVENTPARS" | tr ',' '\n' | awk '{printf "result.args.%s,",$2}' | sed '1,$ s/,$//' >> $2
       echo " );" >> $2
       echo "              } else { console.log(" >> $2
       echo "                   \"${BN}:${EVENTNAME}:\"" >> $2
       echo "$EVENTPARS" | tr ',' '\n' | \
                 awk '\
                      /.*_gwei$/ {s=$2;sub("_gwei$","",s);printf " + \" %s: \" + web3.fromWei(result.args.%s,\"gwei\") + \" gwei\"",s,$2; next} \
                      /.*_ether$/ {s=$2;sub("_ether$","",s);printf " + \" %s: \" + web3.fromWei(result.args.%s,\"ether\") + \" ether\"",s,$2; next} \
                                  {                         printf " + \" %s: \" + result.args.%s ",$2,$2}' >> $2
       echo "              );}" >> $2
       echo "          }});" >> $2
   done
   )
   echo "    }" >> $2
   echo "}" >> $2
}

####################################### Compiler Version #####################################

_createCompilerVersion() { # $1 Script
    SOLCVERSION="`solc --version | grep Version | cut -d : -f 2 | tr -d ' '`"
    echo "function ${BN}CompilerVersion () {" >> $1
    echo "      console.log(\"$SOLCVERSION\");" >> $1
    echo "}" >> $1
}

####################################### Variables ############################################

BN=`basename $1 .sol`
DD=`dirname $1`
DIR=`realpath $DD`
TDIR=$DIR/$BN.dir
ADIR=$DIR/$BN.ana

####################################### COMPILATION ############################################

mkdir -vp $TDIR

echo "######## Compiling $1 to $TDIR #############"

# Select desired EVM version. Either 
# EVMOPT="--evm-version homestead"
# EVMOPT="--evm-version tangerineWhistle"
# EVMOPT="--evm-version spuriousDragon"
# EVMOPT="--evm-version byzantium"
# EVMOPT="--evm-version constantinople"
# EVMOPT="--evm-version petersburg #     (default).

set -x
solc $EVMOPT --overwrite -o $TDIR --gas --asm --bin --abi --opcodes --hashes --userdoc --devdoc $1
RET=$?
set +x

# set -x
# solc --overwrite -o $TDIR --gas --asm --bin --abi --opcodes --hashes --userdoc --devdoc $1
# RET=$?
# set +x

echo "returncode of solc: $RET"

ABI=$TDIR/$BN.abi
BIN=$TDIR/$BN.bin
DEP=$DIR/$BN.deploy.js
USE=$DIR/$BN.use.js

####################################### Ugly patch of $ABI ###################################################

set -x
sed -i '1,$ s/"stateMutability":"payable","type":"function"/"stateMutability":"payable","payable":"true","type":"function"/g' $ABI
set +x

####################################### BACKWARDS COMPILATION (optional) ######################################

if [ "$ANALYSIS" = "yes" -a -f $ABI -a -f $BIN ] && which porosity >/dev/null; then
 echo "######## Decompiling and Analysing $BIN and $ABI"
 mkdir -vp $ADIR
 set -x
 porosity --code-file $BIN --abi "`cat $ABI`" --list > $ADIR/$BN.list
    # cat $ADIR/$BN.list
 porosity --code-file $BIN --abi "`cat $ABI`" --disassm > $ADIR/$BN.disassm
 
 porosity --code-file $BIN --abi "`cat $ABI`" --cfg | grep -v '^[A-Z]' > $ADIR/$BN.gv
 dot -Teps $ADIR/$BN.gv | gs -sDEVICE=pdfwrite -dEPSCrop -o $ADIR/$BN.pdf - >/dev/null

 porosity --code-file $BIN --abi "`cat $ABI`" --cfg-full | grep -v '^[A-Z]' > $ADIR/$BN.full.gv
 dot -Teps $ADIR/$BN.full.gv | gs -sDEVICE=pdfwrite -dEPSCrop -o $ADIR/$BN.full.pdf - >/dev/null
 set +x
fi

####################################### DEPLOY SCRIPT ############################################

echo "######## Generating Deploy Script $DEP #############"

echo "// Deploy script for contract: $1" > $DEP

PARLIST=""

echo >> $DEP
echo "// vvvvvv edit vvvvvvvv" >> $DEP
for PAR in `cat $ABI | jq '.[] | select (.type=="constructor") .inputs[].name' | tr -d '"'`; do
   TYPE=`cat $ABI | jq ".[] | select (.type==\"constructor\") .inputs[] | select (.name==\"$PAR\") .type" | tr -d '"'`
   case $TYPE in
       uint[0-9][0-9]*)
           echo "var $PAR = 256" >> $DEP
           ;;
       *)
           echo "var $PAR = \"DEFAULT $TYPE\"" >> $DEP
           ;;
   esac
   PARLIST="${PARLIST},$PAR"
done
# echo "var gasPriceToPay = '1.9' /* in gweil; comment to use gasPriceToPay from gethrc.js*/" >> $DEP
echo "// ^^^^^^ edit ^^^^^^^^" >> $DEP
echo >> $DEP

PARLIST="`echo $PARLIST | sed '1,$ s/^,//g'`"
if [ "$PARLIST" = "" ]; then PARLIST=""; else PARLIST="$PARLIST,"; fi

echo "                                     p('var ${BN}ABI=...;');" >> $DEP
echo "var ${BN}ABI=`cat $ABI`;" >> $DEP
echo >> $DEP
echo "                                     p('var ${BN}Factory=eth.contract(${BN}ABI)');" >> $DEP
echo "var ${BN}Factory=eth.contract(${BN}ABI);" >> $DEP
echo >> $DEP
echo "                                     p('var ${BN}Code=...;');" >> $DEP
echo "var ${BN}Code=\"0x\"+\"`cat $BIN`\"" >> $DEP
echo >> $DEP
echo "                                     p('var ${BN}Candidate ${BN}Address ${BN}');" >> $DEP
echo "var ${BN}Candidate;" >> $DEP
echo "var ${BN}Address;" >> $DEP
echo "var ${BN};" >> $DEP
echo >> $DEP
_createCompilerVersion $DEP
echo >> $DEP
echo "function ${BN}Init() {" >> $DEP
echo " if ( typeof ${BN} == 'undefined' ) {" >> $DEP
echo "  if (eth.getTransactionReceipt(${BN}Candidate.transactionHash) == null ) {" >> $DEP
echo "      p(\"Not yet mined. contractAddress not yet known. Wait.\");" >> $DEP
echo "      p(\"Call later: ${BN}Init()\");" >> $DEP
echo "  } else {" >> $DEP
echo "      p(\"SUCCESS. Mined. contractAddress is: \" + eth.getTransactionReceipt(${BN}Candidate.transactionHash).contractAddress );" >> $DEP
echo "                                    p(\"${BN}Address = eth.getTransactionReceipt(${BN}Candidate.transactionHash).contractAddress;\");" >> $DEP
echo "      ${BN}Address=eth.getTransactionReceipt(${BN}Candidate.transactionHash).contractAddress;" >> $DEP
echo "                                    p(\"${BN}Address = \" + ${BN}Address);" >> $DEP
echo >> $DEP
echo "                                    p(\"${BN} = eth.contract(${BN}ABI).at(${BN}Address);\");" >> $DEP
echo "      ${BN}=eth.contract(${BN}ABI).at(${BN}Address);" >> $DEP
if [ "$EVENTS" = "yes" ]; then
    echo "      ${BN}WatchEvents();" >> $DEP
fi
echo "  }" >> $DEP
echo " } else { p('$BN already initialized.'); }" >> $DEP
echo "}" >> $DEP
echo >> $DEP
echo "function ${BN}Create () {" >> $DEP
echo "  if ( typeof ${BN}Candidate == 'undefined' ) {" >> $DEP
echo "                                     p('${BN}Candidate=${BN}Factory.new(...)');" >> $DEP
echo "   ${BN}Candidate=${BN}Factory.new(" >> $DEP
echo "                           $PARLIST" >> $DEP
echo "                           {" >> $DEP
echo "                              from : eth.defaultAccount," >> $DEP
echo "                              data: ${BN}Code," >> $DEP
echo "                              gas: '4700000'," >> $DEP
echo "                              gasPrice: web3.toWei(gasPriceToPay, 'gwei')" >> $DEP
echo "                           }," >> $DEP
echo "                           function (e, contract){" >> $DEP
echo "                               console.log(e, contract);" >> $DEP
echo "                               if (typeof contract.address !== 'undefined') {" >> $DEP
echo "                                   console.log('${BN}Factory.new(): Contract $BN mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);" >> $DEP
#echo "                                               p(\"${BN}Init()\");" >> $DEP
#echo "                                   ${BN}Init();" >> $DEP
echo "                               } else {" >> $DEP
echo "                                   console.log('${BN}Factory.new(): Contract transaction sent. Waiting to be mined. transactionHash: ' + contract.transactionHash);" >> $DEP
echo "                               }" >> $DEP
echo "                           }" >> $DEP
echo "                          );" >> $DEP
# echo "  for ( i=0; i<20 && ( typeof ${BN} == 'undefined' ) && (eth.getTransactionReceipt(${BN}Candidate.transactionHash) == null ); i++) {" >> $DEP
echo "  for ( i=0; i<15 && (eth.getTransactionReceipt(${BN}Candidate.transactionHash) == null ); i++) {" >> $DEP
echo "                                        p(\"    waiting...\");" >> $DEP
echo "               admin.sleep(1);" >> $DEP
echo "  }" >> $DEP
echo "                                        p(\"${BN}Init()\");" >> $DEP
echo "   ${BN}Init();" >> $DEP
echo "  } else { p('${BN}Candidate already initialized.'); }" >> $DEP
echo "}" >> $DEP
echo >> $DEP
echo "function ${BN}Check() {" >> $DEP
echo "    if ( typeof ${BN}Candidate != 'undefined' ) {" >> $DEP
echo "   console.log(\"Mining says, contractAddress is: \" + eth.getTransactionReceipt(${BN}Candidate.transactionHash).contractAddress );" >> $DEP
echo "}}" >> $DEP
echo >> $DEP

cat $1 | \
   sed '1,$ s/"/\\"/g' | \
   awk "BEGIN {print \"function ${BN}Source() {\"}{printf \"console.log(\\\"%s\\\")\n\",\$0}END{print \"}\"}" \
   >> $DEP

echo "" >> $DEP

if [ "$EVENTS" = "yes" ]; then
    _createEventCode $1 $DEP
fi

UTILFILE=$DIR/$BN.util.js

if [ -f "$UTILFILE" ]; then
    echo "//                                    p(\"Loading Util Script File $UTILFILE\");" >> $DEP
    echo "//loadScript(\"$UTILFILE\");" >> $DEP
    echo "                                    p(\"Inserted Util Script File $UTILFILE from `date`\");" >> $DEP
    cat $UTILFILE >> $DEP
fi

# if [ -f "$UTILFILE" ]; then
#    echo "                                    p(\"Loading Util Script File $UTILFILE\");" >> $DEP
#    echo "loadScript(\"$UTILFILE\");" >> $DEP
# fi

echo >> $DEP
echo "p(\"----------------------------------------------------\");" >> $DEP
echo "p(\"After unlocking eth.account[0] with unlock(0);\");" >> $DEP
echo "p(\"create ${BN}Candidate and $BN with ${BN}Create();\");" >> $DEP
#echo "                                    p('When mining is completed, you can:');" >> $DEP
#echo "                                    p('Call: $BN.doWhatever(...);');" >> $DEP
#echo "                                    p(\"Call: $BN.doWhatever.sendTransaction({from:eth.defaultAccount, gasPrice:web3.toWei(gasPriceToPay,'gwei')});\");" >> $DEP



####################################### USAGE SCRIPT ############################################

echo "######## Generating Usage Script $USE #############"

echo "// Usage Script for smart contract $1" > $USE
echo >> $USE
echo "// vvvvvv edit vvvvvvvv" >> $USE
echo "var ${BN}Address=\"0x1234 EDIT\"" >> $USE
# echo "var gasPriceToPay = '1.9' /* in gweil; comment to use gasPriceToPay from gethrc.js*/" >> $USE
echo "// ^^^^^^ edit ^^^^^^^^" >> $USE
echo >> $USE
echo "                                    p('var ${BN}Address=\"' + ${BN}Address + '\"');" >> $USE
echo "                                    p('var ${BN}ABI=...;');" >> $USE
echo "var ${BN}ABI=`cat $ABI`;" >> $USE
echo >> $USE
_createCompilerVersion $USE
echo >> $USE
echo "                                     p('var ${BN}Code=...;');" >> $USE
echo "var ${BN}Code=\"0x\"+\"`cat $BIN`\"" >> $USE
echo >> $USE
echo "                                    p('var ${BN}=eth.contract(${BN}ABI).at(${BN}Address);');" >> $USE
echo "var ${BN}=eth.contract(${BN}ABI).at(${BN}Address);" >> $USE
echo "" >> $USE

cat $1 | \
   sed '1,$ s/"/\\"/g' | \
   awk "BEGIN {print \"function ${BN}Source() {\"}{printf \"console.log(\\\"%s\\\")\n\",\$0}END{print \"}\"}" \
   >> $USE

if [ "$EVENTS" = "yes" ]; then
    _createEventCode $1 $USE
    echo "" >> $USE
    echo "p('${BN}WatchEvents();');" >> $USE
    echo "${BN}WatchEvents();" >> $USE
fi

echo "" >> $USE

UTILFILE=$DIR/$BN.util.js

if [ -f "$UTILFILE" ]; then
    echo "//                                    p(\"Loading Util Script File $UTILFILE\");" >> $USE
    echo "//loadScript(\"$UTILFILE\");" >> $USE
    echo "                                    p(\"Inserted Util Script File $UTILFILE from `date`\");" >> $USE
    cat $UTILFILE >> $USE
fi



# echo >> $DEP
# echo "                                    p('call $BN.doWhatever(...);');" >> $USE
# echo "                                    p(\"call $BN.doWhatever.sendTransaction({from:eth.defaultAccount, gasPrice:web3.toWei(gasPriceToPay,'gwei')});\");" >> $USE


echo "Finished. You may have to edit $DEP, then call gjsa $DEP to deploy"

