{-# Language CPP #-}

#ifdef __GHCJS__
{-# Language JavaScriptFFI #-}
#endif

module EVM.Keccak (keccak, abiKeccak, newContractAddress) where

import EVM.Types

import Control.Arrow ((>>>))

import Data.Bits
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Word

#ifdef __GHCJS__
import qualified Data.JSString as JS
import qualified Data.Text.Encoding as Text
import qualified Data.Text as Text
import qualified Data.ByteString.Base64 as BS64

foreign import javascript unsafe
  "keccakBase64($1)"
  keccakBase64 :: JS.JSString -> JS.JSString

keccakBytes =
  BS64.encode
    >>> Text.decodeUtf8
    >>> Text.unpack
    >>> JS.pack
    >>> keccakBase64
    >>> JS.unpack
    >>> Text.pack
    >>> Text.encodeUtf8
    >>> BS64.decodeLenient

#else
import Crypto.Hash
import qualified Data.ByteArray as BA

keccakBytes =
  (hash :: ByteString -> Digest Keccak_256)
    >>> BA.unpack
    >>> BS.pack

#endif

keccakBytes :: ByteString -> ByteString

word32 :: [Word8] -> Word32
word32 xs = sum [ fromIntegral x `shiftL` (8*n)
                | (n, x) <- zip [0..] (reverse xs) ]

octets :: W256 -> [Word8]
octets x =
  dropWhile (== 0) [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..31]]

octets160 :: Addr -> [Word8]
octets160 x =
  dropWhile (== 0) [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..19]]

keccak :: ByteString -> W256
keccak =
  keccakBytes
    >>> BS.take 32
    >>> word

abiKeccak :: ByteString -> Word32
abiKeccak =
  keccakBytes
    >>> BS.take 4
    >>> BS.unpack
    >>> word32

rlpWord256 :: W256 -> ByteString
rlpWord256 0 = BS.pack [0x80]
rlpWord256 x | x <= 0x7f = BS.pack [fromIntegral x]
rlpWord256 x =
  let xs = octets x
  in BS.pack ([0x80 + fromIntegral (length xs)] ++ xs)

rlpWord160 :: Addr -> ByteString
rlpWord160 0 = BS.pack [0x80]
rlpWord160 x =
  let xs = octets160 x
  in BS.pack ([0x80 + fromIntegral (length xs)] ++ xs)

rlpList :: [ByteString] -> ByteString
rlpList xs =
  let n = sum (map BS.length xs)
  in if n <= 55
     then BS.cons (fromIntegral (0xc0 + n)) (BS.concat xs)
     else
       let ns = rlpWord256 (fromIntegral n)
       in BS.cons (fromIntegral (0xf7 + BS.length ns)) (BS.concat (ns : xs))

newContractAddress :: Addr -> W256 -> Addr
newContractAddress a n =
  fromIntegral
    (keccak $ rlpList [rlpWord160 a, rlpWord256 n])
