{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards       #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeSynonymInstances  #-}

-- |
-- Module      :  Network.Ethereum.Account.Default
-- Copyright   :  Alexander Krupenkin 2018
-- License     :  BSD3
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  unportable
--
-- Default node managed account (typically the first of accounts list).
--

module Network.Ethereum.Account.Default where

import           Control.Monad.Catch               (throwM)
import           Control.Monad.State.Strict        (get, runStateT)
import           Control.Monad.Trans               (MonadTrans (..))
import qualified Data.ByteArray                    as BA (convert)
import           Data.Maybe                        (listToMaybe)
import           Data.Monoid                       ((<>))
import           Data.Proxy                        (Proxy (..))

import           Data.Solidity.Abi.Codec           (decode, encode)
import           Network.Ethereum.Account.Class    (Account (..))
import           Network.Ethereum.Account.Internal (AccountT (..),
                                                    CallParam (..),
                                                    defaultCallParam, getCall,
                                                    getReceipt)
import qualified Network.Ethereum.Api.Eth          as Eth (accounts, call,
                                                           estimateGas,
                                                           sendTransaction)
import           Network.Ethereum.Api.Provider     (Web3Error (ParserFail))
import           Network.Ethereum.Api.Types        (Call (callData, callFrom, callGas))
import           Network.Ethereum.Contract.Method  (Method (..))

type DefaultAccount = AccountT ()

instance Account () DefaultAccount where
    withAccount _ =
        fmap fst . flip runStateT (defaultCallParam ()) . runAccountT

    send (args :: a) = do
        c <- getCall
        lift $ do
            accounts <- Eth.accounts
            let dat = selector (Proxy :: Proxy a) <> encode args
                params = c { callData = Just $ BA.convert dat
                           , callFrom = listToMaybe accounts }

            params' <- case callGas params of
                Just _  -> return params
                Nothing -> do
                    gasLimit <- Eth.estimateGas params
                    return $ params { callGas = Just gasLimit }

            getReceipt =<< Eth.sendTransaction params'

    call (args :: a) = do
        c <- getCall
        CallParam{..} <- get
        res <- lift $ do
            accounts <- Eth.accounts
            let dat    = selector (Proxy :: Proxy a) <> encode args
                params = c { callData = Just $ BA.convert dat
                           , callFrom = listToMaybe accounts }
            Eth.call params _block
        case decode res of
            Right r -> return r
            Left e  -> lift $ throwM (ParserFail e)