{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Module      :  Network.Ethereum.Contract.Method
-- Copyright   :  Alexander Krupenkin 2016-2018
-- License     :  BSD3
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  unportable
--
-- Ethereum contract method support.
--

module Network.Ethereum.Contract.Method (
    Method(..)
  , call
  , sendTx
  ) where

import           Control.Monad.Catch             (throwM)
import           Data.Monoid                     ((<>))
import           Data.Proxy                      (Proxy (..))

import           Network.Ethereum.ABI.Class      (ABIGet, ABIPut, ABIType (..))
import           Network.Ethereum.ABI.Codec      (decode, encode)
import           Network.Ethereum.ABI.Prim.Bytes (Bytes)
import qualified Network.Ethereum.Web3.Eth       as Eth
import           Network.Ethereum.Web3.Provider  (Web3, Web3Error (ParserFail))
import           Network.Ethereum.Web3.Types     (Call (callData), DefaultBlock,
                                                  TxHash)

class ABIPut a => Method a where
  selector :: Proxy a -> Bytes

instance ABIType () where
  isDynamic _ = False

instance ABIPut ()

-- | Send transaction without method selection
instance Method () where
  selector = mempty

-- | 'sendTx' is used to submit a state changing transaction.
sendTx :: Method a
       => Call
       -- ^ Call configuration
       -> a
       -- ^ method data
       -> Web3 TxHash
sendTx call' (dat :: a) =
    let sel = selector (Proxy :: Proxy a)
    in Eth.sendTransaction (call' { callData = Just $ sel <> encode dat })

-- | 'call' is used to call contract methods that have no state changing effects.
call :: (Method a, ABIGet b)
     => Call
     -- ^ Call configuration
     -> DefaultBlock
     -- ^ State mode for constant call (latest or pending)
     -> a
     -- ^ Method data
     -> Web3 b
     -- ^ 'Web3' wrapped result
call call' mode (dat :: a) = do
    let sel = selector (Proxy :: Proxy a)
    res <- Eth.call (call' { callData = Just $ sel <> encode dat }) mode
    case decode res of
        Left e  -> throwM $ ParserFail $ "Unable to parse response: " ++ e
        Right x -> return x