{-# Language TemplateHaskell #-}
module EVM.Dapp where
import EVM (Trace, traceCodehash, traceOpIx)
import EVM.ABI (Event)
import EVM.Debug (srcMapCodePos)
import EVM.Keccak (abiKeccak)
import EVM.Solidity (SolcContract, CodeType (..), SourceCache, SrcMap)
import EVM.Solidity (contractName)
import EVM.Solidity (runtimeCodehash, creationCodehash, abiMap)
import EVM.Solidity (runtimeSrcmap, creationSrcmap, eventMap)
import EVM.Solidity (methodSignature, contractAst, astIdMap, astSrcMap)
import EVM.Types (W256)
import Data.Aeson (Value)
import Data.Text (Text, isPrefixOf, pack)
import Data.Text.Encoding (encodeUtf8)
import Data.Map (Map)
import Data.Monoid ((<>))
import Data.Word (Word32)
import Data.List (sort)
import Control.Arrow ((>>>))
import Control.Lens
import qualified Data.Map as Map
data DappInfo = DappInfo
  { _dappRoot       :: FilePath
  , _dappSolcByName :: Map Text SolcContract
  , _dappSolcByHash :: Map W256 (CodeType, SolcContract)
  , _dappSources    :: SourceCache
  , _dappUnitTests  :: [(Text, [Text])]
  , _dappEventMap   :: Map W256 Event
  , _dappAstIdMap   :: Map Int Value
  , _dappAstSrcMap  :: (SrcMap -> Maybe Value)
  }
makeLenses ''DappInfo
dappInfo
  :: FilePath -> Map Text SolcContract -> SourceCache -> DappInfo
dappInfo root solcByName sources =
  let
    solcs = Map.elems solcByName
    astIds = astIdMap (map (view contractAst) solcs)
  in DappInfo
    { _dappRoot = root
    , _dappUnitTests = findUnitTests ("test" `isPrefixOf`) solcs
    , _dappSources = sources
    , _dappSolcByName = solcByName
    , _dappSolcByHash =
        let
          f g k = Map.fromList [(view g x, (k, x)) | x <- solcs]
        in
          mappend
           (f runtimeCodehash  Runtime)
           (f creationCodehash Creation)
    , _dappEventMap =
        
        mconcat (map (view eventMap) solcs)
    , _dappAstIdMap  = astIds
    , _dappAstSrcMap = astSrcMap astIds
    }
unitTestMarkerAbi :: Word32
unitTestMarkerAbi = abiKeccak (encodeUtf8 "IS_TEST()")
findUnitTests :: (Text -> Bool) -> ([SolcContract] -> [(Text, [Text])])
findUnitTests matcher =
  concatMap $ \c ->
    case preview (abiMap . ix unitTestMarkerAbi) c of
      Nothing -> []
      Just _  ->
        let testNames = (unitTestMethods matcher) c
        in if null testNames
           then []
           else [(view contractName c, testNames)]
unitTestMethods :: (Text -> Bool) -> (SolcContract -> [Text])
unitTestMethods matcher =
  view abiMap
    >>> Map.elems
    >>> map (view methodSignature)
    >>> filter matcher
    >>> sort
traceSrcMap :: DappInfo -> Trace -> Maybe SrcMap
traceSrcMap dapp trace =
  let
    h = view traceCodehash trace
    i = view traceOpIx trace
  in case preview (dappSolcByHash . ix h) dapp of
    Nothing ->
      Nothing
    Just (Creation, solc) ->
      preview (creationSrcmap . ix i) solc
    Just (Runtime, solc) ->
      preview (runtimeSrcmap . ix i) solc
showTraceLocation :: DappInfo -> Trace -> Either Text Text
showTraceLocation dapp trace =
  case traceSrcMap dapp trace of
    Nothing -> Left "<no source map>"
    Just sm ->
      case srcMapCodePos (view dappSources dapp) sm of
        Nothing -> Left "<source not found>"
        Just (fileName, lineIx) ->
          Right (fileName <> ":" <> pack (show lineIx))