{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell            #-}
-- |
-- Module      :  Network.Ethereum.Web3.Types
-- Copyright   :  Alexander Krupenkin 2016
-- License     :  BSD3
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  portable
--
-- Commonly used types and instances.
--
module Network.Ethereum.Web3.Types where

import           Control.Exception              (Exception)
import           Control.Monad.IO.Class         (MonadIO)
import           Data.Aeson
import           Data.Aeson.TH
import           Data.Monoid                    ((<>))
import           Data.Text                      (Text)
import qualified Data.Text.Lazy.Builder         as B
import qualified Data.Text.Lazy.Builder.Int     as B
import qualified Data.Text.Read                 as R
import           Data.Typeable                  (Typeable)
import           GHC.Generics
import           Network.Ethereum.Web3.Address  (Address)
import           Network.Ethereum.Web3.Internal (toLowerFirst)

-- | Any communication with Ethereum node wrapped with 'Web3' monad
newtype Web3 a b = Web3 { unWeb3 :: IO b }
  deriving (Functor, Applicative, Monad, MonadIO)

-- | Some peace of error response
data Web3Error
  = JsonRpcFail !RpcError
  -- ^ JSON-RPC communication error
  | ParserFail  !String
  -- ^ Error in parser state
  | UserFail    !String
  -- ^ Common head for user errors
  deriving (Typeable, Show, Eq, Generic)

instance Exception Web3Error

-- | JSON-RPC error message
data RpcError = RpcError
  { errCode    :: !Int
  , errMessage :: !Text
  , errData    :: !(Maybe Value)
  } deriving (Show, Eq, Generic)

$(deriveJSON (defaultOptions
    { fieldLabelModifier = toLowerFirst . drop 3 }) ''RpcError)

-- | Low-level event filter data structure
data Filter = Filter
  { filterAddress   :: !(Maybe Address)
  , filterTopics    :: !(Maybe [Maybe Text])
  , filterFromBlock :: !(Maybe Text)
  , filterToBlock   :: !(Maybe Text)
  } deriving (Show, Generic)

$(deriveJSON (defaultOptions
    { fieldLabelModifier = toLowerFirst . drop 6 }) ''Filter)

-- | Event filter identifier
newtype FilterId = FilterId Integer
  deriving (Show, Eq, Ord, Generic)

instance FromJSON FilterId where
    parseJSON (String v) =
        case R.hexadecimal v of
            Right (x, "") -> return (FilterId x)
            _             -> fail "Unable to parse FilterId!"
    parseJSON _ = fail "The string is required!"

instance ToJSON FilterId where
    toJSON (FilterId x) =
        let hexValue = B.toLazyText (B.hexadecimal x)
        in  toJSON ("0x" <> hexValue)

-- | Changes pulled by low-level call 'eth_getFilterChanges', 'eth_getLogs',
-- and 'eth_getFilterLogs'
data Change = Change
  { changeLogIndex         :: !Text
  , changeTransactionIndex :: !Text
  , changeTransactionHash  :: !Text
  , changeBlockHash        :: !Text
  , changeBlockNumber      :: !Text
  , changeAddress          :: !Address
  , changeData             :: !Text
  , changeTopics           :: ![Text]
  } deriving (Show, Generic)

$(deriveJSON (defaultOptions
    { fieldLabelModifier = toLowerFirst . drop 6 }) ''Change)

-- | The contract call params
data Call = Call
  { callFrom     :: !(Maybe Address)
  , callTo       :: !Address
  , callGas      :: !(Maybe Text)
  , callGasPrice:: !(Maybe Text)
  , callValue    :: !(Maybe Text)
  , callData     :: !(Maybe Text)
  } deriving (Show, Generic)

$(deriveJSON (defaultOptions
    { fieldLabelModifier = toLowerFirst . drop 4
    , omitNothingFields = True }) ''Call)

-- | The contract call mode describe used state: latest or pending
data DefaultBlock = BlockNumberHex Text | Earliest | Latest | Pending
  deriving (Show, Eq)

instance ToJSON DefaultBlock where
    toJSON (BlockNumberHex hex) = toJSON hex
    toJSON parameter            = toJSON . toLowerFirst . show $ parameter

-- TODO: Wrap
-- | Transaction hash text string
type TxHash = Text

-- | Transaction information
data Transaction = Transaction
  { txHash             :: !TxHash
  -- ^ DATA, 32 Bytes - hash of the transaction.
  , txNonce            :: !Text
  -- ^ QUANTITY - the number of transactions made by the sender prior to this one.
  , txBlockHash        :: !Text
  -- ^ DATA, 32 Bytes - hash of the block where this transaction was in. null when its pending.
  , txBlockNumber      :: !Text
  -- ^ QUANTITY - block number where this transaction was in. null when its pending.
  , txTransactionIndex :: !Text
  -- ^ QUANTITY - integer of the transactions index position in the block. null when its pending.
  , txFrom             :: !Address
  -- ^ DATA, 20 Bytes - address of the sender.
  , txTo               :: !(Maybe Address)
  -- ^ DATA, 20 Bytes - address of the receiver. null when its a contract creation transaction.
  , txValue            :: !Text
  -- ^ QUANTITY - value transferred in Wei.
  , txGasPrice         :: !Text
  -- ^ QUANTITY - gas price provided by the sender in Wei.
  , txGas              :: !Text
  -- ^ QUANTITY - gas provided by the sender.
  , txInput            :: !Text
  -- ^ DATA - the data send along with the transaction.
  } deriving (Show, Generic)

$(deriveJSON (defaultOptions
    { fieldLabelModifier = toLowerFirst . drop 2 }) ''Transaction)

-- | Block information
data Block = Block
  { blockNumber           :: !Text
  -- ^ QUANTITY - the block number. null when its pending block.
  , blockHash             :: !Text
  -- ^ DATA, 32 Bytes - hash of the block. null when its pending block.
  , blockParentHash       :: !Text
  -- ^ DATA, 32 Bytes - hash of the parent block.
  , blockNonce            :: !(Maybe Text)
  -- ^ DATA, 8 Bytes - hash of the generated proof-of-work. null when its pending block.
  , blockSha3Uncles       :: !Text
  -- ^ DATA, 32 Bytes - SHA3 of the uncles data in the block.
  , blockLogsBloom        :: !Text
  -- ^ DATA, 256 Bytes - the bloom filter for the logs of the block. null when its pending block.
  , blockTransactionsRoot :: !Text
  -- ^ DATA, 32 Bytes - the root of the transaction trie of the block.
  , blockStateRoot        :: !Text
  -- ^ DATA, 32 Bytes - the root of the final state trie of the block.
  , blockReceiptRoot      :: !(Maybe Text)
  -- ^ DATA, 32 Bytes - the root of the receipts trie of the block.
  , blockMiner            :: !Address
  -- ^ DATA, 20 Bytes - the address of the beneficiary to whom the mining rewards were given.
  , blockDifficulty       :: !Text
  -- ^ QUANTITY - integer of the difficulty for this block.
  , blockTotalDifficulty  :: !Text
  -- ^ QUANTITY - integer of the total difficulty of the chain until this block.
  , blockExtraData        :: !Text
  -- ^ DATA - the "extra data" field of this block.
  , blockSize             :: !Text
  -- ^ QUANTITY - integer the size of this block in bytes.
  , blockGasLimit         :: !Text
  -- ^ QUANTITY - the maximum gas allowed in this block.
  , blockGasUsed          :: !Text
  -- ^ QUANTITY - the total used gas by all transactions in this block.
  , blockTimestamp        :: !Text
  -- ^ QUANTITY - the unix timestamp for when the block was collated.
  , blockTransactions     :: ![Transaction]
  -- ^ Array of transaction objects.
  , blockUncles           :: ![Text]
  -- ^ Array - Array of uncle hashes.
  } deriving (Show, Generic)

$(deriveJSON (defaultOptions
    { fieldLabelModifier = toLowerFirst . drop 5 }) ''Block)