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

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

-- | 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)

instance Exception Web3Error

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

$(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

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

-- | Event filder ident
newtype FilterId = FilterId Integer
  deriving (Show, Eq, Ord)

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'
data Change = Change
  { changeLogIndex         :: !Text
  , changeTransactionIndex :: !Text
  , changeTransactionHash  :: !Text
  , changeBlockHash        :: !Text
  , changeBlockNumber      :: !Text
  , changeAddress          :: !Address
  , changeData             :: !Text
  , changeTopics           :: ![Text]
  } deriving Show

$(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

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

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

instance ToJSON CallMode where
    toJSON = toJSON . toLowerFirst . show

-- 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

$(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

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