module Network.Haskoin.Wallet.KeyRing
(
-- *Database KeyRings
  initWallet
, newKeyRing
, keyRings
, keyRingSource
, getKeyRing

-- *Database Accounts
, accounts
, accountSource
, newAccount
, addAccountKeys
, getAccount
, isMultisigAccount
, isReadAccount
, isCompleteAccount

-- *Database Addresses
, getAddress
, addressSourceAll
, addressSource
, addressPage
, unusedAddresses
, addressCount
, setAddrLabel
, addressPrvKey
, useAddress
, generateAddrs
, setAccountGap
, firstAddrTime
, getPathRedeem
, getPathPubKey

-- *Database Bloom Filter
, getBloomFilter

-- * Helpers
, subSelectAddrCount
) where

import Control.Monad (unless, when, liftM)
import Control.Monad.Trans (MonadIO, liftIO)
import Control.Monad.Base (MonadBase)
import Control.Monad.Catch (MonadThrow)
import Control.Monad.Trans.Resource (MonadResource)
import Control.Exception (throwIO, throw)

import Data.Text (Text, unpack)
import Data.Maybe (mapMaybe, listToMaybe)
import Data.Time.Clock (getCurrentTime)
import Data.Conduit (Source, mapOutput, await, ($$))
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
import Data.List (nub)
import Data.Word (Word32)
import qualified Data.ByteString as BS (ByteString, null)

import qualified Database.Persist as P (updateWhere, update , (=.))
import Database.Esqueleto
    ( Value(..), SqlExpr, SqlQuery
    , InnerJoin(..), on
    , select, from, where_, val, sub_select, countRows, count, unValue
    , orderBy, limit, asc, desc, offset, selectSource, get
    , max_, not_, isNothing, case_, when_, then_, else_
    , (^.), (==.), (&&.), (>.), (-.), (<.)
    -- Reexports from Database.Persist
    , SqlPersistT, Entity(..)
    , getBy, insertUnique, insertMany_, insert_
    )

import Network.Haskoin.Crypto
import Network.Haskoin.Block
import Network.Haskoin.Script
import Network.Haskoin.Node
import Network.Haskoin.Util
import Network.Haskoin.Constants
import Network.Haskoin.Node.HeaderTree

import Network.Haskoin.Wallet.Types
import Network.Haskoin.Wallet.Model

{- Initialization -}

initWallet :: MonadIO m => Double -> SqlPersistT m ()
initWallet fpRate = do
    prevConfigRes <- select $ from $ \c -> return $ count $ c ^. KeyRingConfigId
    let cnt = maybe 0 unValue $ listToMaybe prevConfigRes
    if cnt == (0 :: Int)
        then do
            time <- liftIO getCurrentTime
            -- Create an initial bloom filter
            -- TODO: Compute a random nonce
            let bloom = bloomCreate (filterLen 0) fpRate 0 BloomUpdateNone
            insert_ $ KeyRingConfig
                { keyRingConfigHeight      = 0
                , keyRingConfigBlock       = headerHash genesisHeader
                , keyRingConfigBloomFilter = bloom
                , keyRingConfigBloomElems  = 0
                , keyRingConfigBloomFp     = fpRate
                , keyRingConfigVersion     = 1
                , keyRingConfigCreated     = time
                }
        else return () -- Nothing to do

{- KeyRing -}

-- | Create a new KeyRing from a seed
newKeyRing :: MonadIO m
           => KeyRingName
           -> BS.ByteString
           -> SqlPersistT m (Entity KeyRing)
newKeyRing name seed
    | BS.null seed = liftIO . throwIO $ WalletException "The seed is empty"
    | otherwise = do
        now <- liftIO getCurrentTime
        let keyRing = KeyRing
                    { keyRingName    = name
                    , keyRingMaster  = makeXPrvKey seed
                    , keyRingCreated = now
                    }
        insertUnique keyRing >>= \resM -> case resM of
            Just ki -> return (Entity ki keyRing)
            _ -> liftIO . throwIO $ WalletException $ unwords
                [ "KeyRing", unpack name, "already exists" ]

keyRings :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
         => SqlPersistT m [KeyRing]
keyRings = liftM (map entityVal) $ select $ from return

-- | Stream all KeyRings
keyRingSource :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
              => Source (SqlPersistT m) KeyRing
keyRingSource = mapOutput entityVal $ selectSource $ from return

-- Helper functions to get a KeyRing if it exists, or throw an exception
-- otherwise.
getKeyRing :: MonadIO m => KeyRingName -> SqlPersistT m (Entity KeyRing)
getKeyRing name = getBy (UniqueKeyRing name) >>= \resM -> case resM of
    Just keyRingEnt -> return keyRingEnt
    _ -> liftIO . throwIO $ WalletException $ unwords
        [ "KeyRing", unpack name, "does not exist." ]

{- Account -}

-- | Fetch all the accounts in a keyring
accounts :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
         => KeyRingId -> SqlPersistT m [KeyRingAccount]
accounts ki = liftM (map entityVal) $ select $ accountsFrom ki

-- | Stream all accounts in a keyring
accountSource :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
              => KeyRingId -> Source (SqlPersistT m) KeyRingAccount
accountSource ki = mapOutput entityVal $ selectSource $ accountsFrom ki

accountsFrom :: KeyRingId -> SqlQuery (SqlExpr (Entity KeyRingAccount))
accountsFrom ki =
    from $ \a -> do
        where_ $ a ^. KeyRingAccountKeyRing ==. val ki
        return a

-- | Create a new account
newAccount :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
           => Entity KeyRing
           -> AccountName
           -> AccountType
           -> [XPubKey]
           -> SqlPersistT m (Entity KeyRingAccount)
newAccount (Entity ki keyRing) accountName accountType extraKeys = do
    unless (validAccountType accountType) $
        liftIO . throwIO $ WalletException "Invalid account type"

    -- Get the next account derivation
    derivM <- if accountTypeRead accountType then return Nothing else
        liftM Just $ nextAccountDeriv ki

    -- Derive the next account key
    let f d  = [ deriveXPubKey (derivePath d $ keyRingMaster keyRing) ]
        keys = (maybe [] f derivM) ++ extraKeys

    -- Build the account
    now <- liftIO getCurrentTime
    let acc = KeyRingAccount
            { keyRingAccountKeyRing      = ki
            , keyRingAccountName         = accountName
            , keyRingAccountType         = accountType
            , keyRingAccountDerivation   = derivM
            , keyRingAccountKeys         = keys
            , keyRingAccountGap          = 0
            , keyRingAccountCreated      = now
            }

    -- Check if all the keys are valid
    unless (isValidAccKeys acc) $
        liftIO . throwIO $ WalletException "Invalid account keys"

    -- Insert our account in the database
    let canSetGap = isCompleteAccount acc
        newAcc    = acc{ keyRingAccountGap = if canSetGap then 10 else 0 }

    insertUnique newAcc >>= \resM -> case resM of
        -- The account got created.
        Just ai -> do
            let accE = Entity ai newAcc
            -- If we can set the gap, create the gap addresses
            when canSetGap $ do
                _ <- createAddrs accE AddressExternal 20
                _ <- createAddrs accE AddressInternal 20
                return ()
            return accE
        -- The account already exists
        Nothing -> liftIO . throwIO $ WalletException $ unwords
            [ "Account", unpack accountName, "already exists" ]

-- | Add new thirdparty keys to a multisignature account. This function can
-- fail if the multisignature account already has all required keys.
addAccountKeys :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
               => Entity KeyRingAccount -- ^ Account Entity
               -> [XPubKey]             -- ^ Thirdparty public keys to add
               -> SqlPersistT m KeyRingAccount -- ^ Account information
addAccountKeys (Entity ai acc) keys
    -- We can only add keys on incomplete accounts
    | isCompleteAccount acc = liftIO . throwIO $
        WalletException "The account is already complete"
    | null keys || (not $ isValidAccKeys accKeys) = liftIO . throwIO $
        WalletException "Invalid account keys"
    | otherwise = do
        let canSetGap = isCompleteAccount accKeys
            updGap = if canSetGap then [ KeyRingAccountGap P.=. 10 ] else []
            newAcc = accKeys{ keyRingAccountGap = if canSetGap then 10 else 0 }
        -- Update the account with the keys and the new gap if it is complete
        P.update ai $ (KeyRingAccountKeys P.=. newKeys) : updGap
        -- If we can set the gap, create the gap addresses
        when canSetGap $ do
            let accE = Entity ai newAcc
            _ <- createAddrs accE AddressExternal 20
            _ <- createAddrs accE AddressInternal 20
            return ()
        return newAcc
  where
    newKeys = keyRingAccountKeys acc ++ keys
    accKeys = acc{ keyRingAccountKeys = newKeys }

isValidAccKeys :: KeyRingAccount -> Bool
isValidAccKeys KeyRingAccount{..} = case keyRingAccountType of
    AccountRegular _        -> length keyRingAccountKeys == 1
    -- read-only accounts can have 0 keys. Otherwise 1 key is required.
    AccountMultisig r _ n   -> goMultisig n (if r then 0 else 1)
  where
    goMultisig n minLen =
        length keyRingAccountKeys == length (nub keyRingAccountKeys) &&
        length keyRingAccountKeys <= n &&
        length keyRingAccountKeys >= minLen

-- | Compute the next derivation path for a new account
nextAccountDeriv :: MonadIO m => KeyRingId -> SqlPersistT m HardPath
nextAccountDeriv ki = do
    lastRes <- select $ from $ \a -> do
        where_ (   a ^. KeyRingAccountKeyRing ==. val ki
               &&. not_ (isNothing (a ^. KeyRingAccountDerivation))
               )
        orderBy [ desc (a ^. KeyRingAccountId) ]
        limit 1
        return $ a ^. KeyRingAccountDerivation
    return $ case lastRes of
        (Value (Just (prev :| i)):_) -> prev :| (i + 1)
        _ -> Deriv :| 0

-- Helper functions to get an Account if it exists, or throw an exception
-- otherwise.
getAccount :: MonadIO m => KeyRingName -> AccountName
           -> SqlPersistT m (KeyRing, Entity KeyRingAccount)
getAccount keyRingName accountName = do
    as <- select $ from $ \(k `InnerJoin` a) -> do
        on $ a ^. KeyRingAccountKeyRing ==. k ^. KeyRingId
        where_ (   k ^. KeyRingName        ==. val keyRingName
               &&. a ^. KeyRingAccountName ==. val accountName
               )
        return (k, a)
    case as of
        ((Entity _ k, accEnt):_) -> return (k, accEnt)
        _ -> liftIO . throwIO $ WalletException $ unwords
            [ "Account", unpack accountName, "does not exist" ]

{- Addresses -}

-- | Get an address if it exists, or throw an exception otherwise. Fetching
-- addresses in the hidden gap will also throw an exception.
getAddress :: MonadIO m
           => Entity KeyRingAccount              -- ^ Account Entity
           -> AddressType                        -- ^ Address type
           -> KeyIndex                           -- ^ Derivation index (key)
           -> SqlPersistT m (Entity KeyRingAddr) -- ^ Address
getAddress accE@(Entity ai _) addrType index = do
    res <- select $ from $ \x -> do
        where_ (   x ^. KeyRingAddrAccount ==. val ai
               &&. x ^. KeyRingAddrType    ==. val addrType
               &&. x ^. KeyRingAddrIndex   ==. val index
               &&. x ^. KeyRingAddrIndex   <.  subSelectAddrCount accE addrType
               )
        limit 1
        return x
    case res of
        (addrE:_) -> return addrE
        _ -> liftIO . throwIO $ WalletException $ unwords
            [ "Invalid address index", show index ]

-- | Stream all addresses in the wallet, including hidden gap addresses. This
-- is useful for building a bloom filter.
addressSourceAll :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
                 => Source (SqlPersistT m) KeyRingAddr
addressSourceAll = mapOutput entityVal $ selectSource $ from return

-- | Stream all addresses in one account. Hidden gap addresses are not included.
addressSource :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
              => Entity KeyRingAccount -- ^ Account Entity
              -> AddressType           -- ^ Address Type
              -> Source (SqlPersistT m) KeyRingAddr -- ^ Source of addresses
addressSource accE@(Entity ai _) addrType = mapOutput entityVal $
    selectSource $ from $ \x -> do
        where_ (   x ^. KeyRingAddrAccount ==. val ai
               &&. x ^. KeyRingAddrType    ==. val addrType
               &&. x ^. KeyRingAddrIndex   <.  subSelectAddrCount accE addrType
               )
        return x

-- | Get addresses by pages.
addressPage :: MonadIO m
            => Entity KeyRingAccount -- ^ Account Entity
            -> AddressType           -- ^ Address type
            -> PageRequest           -- ^ Page request
            -> SqlPersistT m ([KeyRingAddr], Word32)
                -- ^ Page result
addressPage accE@(Entity ai _) addrType page@PageRequest{..}
    | validPageRequest page = do
        cnt <- addressCount accE addrType

        let (d, m)  = cnt `divMod` pageLen
            maxPage = max 1 $ d + min 1 m

        when (pageNum > maxPage) $ liftIO . throwIO $ WalletException $
            unwords [ "Invalid page number", show pageNum ]

        if cnt == 0 then return ([], maxPage) else do
            res <- liftM (map entityVal) $ select $ from $ \x -> do
                where_ (   x ^. KeyRingAddrAccount ==. val ai
                       &&. x ^. KeyRingAddrType    ==. val addrType
                       &&. x ^. KeyRingAddrIndex   <.  val cnt
                       )
                let order = if pageReverse then asc else desc
                orderBy [ order (x ^. KeyRingAddrIndex) ]
                limit $ fromIntegral pageLen
                offset $ fromIntegral $ (pageNum - 1) * pageLen
                return x

            -- Flip the order back to ASC if we had it DEC
            let f = if pageReverse then id else reverse
            return (f res, maxPage)

    | otherwise = liftIO . throwIO $ WalletException $
        concat [ "Invalid page request"
               , " (Page: ", show pageNum, ", Page size: ", show pageLen, ")"
               ]

-- | Get a count of all the addresses in an account
addressCount :: MonadIO m
             => Entity KeyRingAccount -- ^ Account Entity
             -> AddressType           -- ^ Address type
             -> SqlPersistT m Word32  -- ^ Address Count
addressCount (Entity ai acc) addrType = do
    res <- select $ from $ \x -> do
        where_ (   x ^. KeyRingAddrAccount ==. val ai
               &&. x ^. KeyRingAddrType    ==. val addrType
               )
        return countRows
    let cnt = maybe 0 unValue $ listToMaybe res
    return $ if cnt > keyRingAccountGap acc
        then cnt - keyRingAccountGap acc
        else 0

-- | Get a list of all unused addresses.
unusedAddresses :: MonadIO m
                => Entity KeyRingAccount -- ^ Account ID
                -> AddressType           -- ^ Address type
                -> SqlPersistT m [KeyRingAddr] -- ^ Unused addresses
unusedAddresses (Entity ai acc) addrType = do
    liftM (reverse . map entityVal) $ select $ from $ \x -> do
        where_ (   x ^. KeyRingAddrAccount ==. val ai
               &&. x ^. KeyRingAddrType    ==. val addrType
               )
        orderBy [ desc $ x ^. KeyRingAddrIndex ]
        limit $ fromIntegral $ keyRingAccountGap acc
        offset $ fromIntegral $ keyRingAccountGap acc
        return x

-- | Add a label to an address.
setAddrLabel :: MonadIO m
             => Entity KeyRingAccount -- ^ Account ID
             -> KeyIndex              -- ^ Derivation index
             -> AddressType           -- ^ Address type
             -> Text                  -- ^ New label
             -> SqlPersistT m KeyRingAddr
setAddrLabel accE i addrType label = do
    Entity addrI addr <- getAddress accE addrType i
    P.update addrI [ KeyRingAddrLabel P.=. label ]
    return $ addr{ keyRingAddrLabel = label }

-- | Returns the private key of an address.
addressPrvKey :: MonadIO m
              => KeyRing               -- ^ KeyRing
              -> Entity KeyRingAccount -- ^ Account Entity
              -> KeyIndex              -- ^ Derivation index of the address
              -> AddressType           -- ^ Address type
              -> SqlPersistT m PrvKeyC -- ^ Private key
addressPrvKey keyRing accE@(Entity ai _) index addrType = do
    res <- select $ from $ \x -> do
        where_ (   x ^. KeyRingAddrAccount ==. val ai
               &&. x ^. KeyRingAddrType    ==. val addrType
               &&. x ^. KeyRingAddrIndex   ==. val index
               &&. x ^. KeyRingAddrIndex   <.  subSelectAddrCount accE addrType
               )
        return (x ^. KeyRingAddrFullDerivation)
    case res of
        (Value (Just deriv):_) ->
            return $ xPrvKey $ derivePath deriv $ keyRingMaster keyRing
        _ -> liftIO . throwIO $ WalletException "Invalid address"

-- | Create new addresses in an account and increment the internal bloom filter.
-- This is a low-level function that simply creates the desired amount of new
-- addresses in an account, disregarding visible and hidden address gaps. You
-- should use the function `setAccountGap` if you want to control the gap of an
-- account instead.
createAddrs :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
            => Entity KeyRingAccount
            -> AddressType
            -> Word32
            -> SqlPersistT m [KeyRingAddr]
createAddrs (Entity ai acc) addrType n
    | n == 0 = liftIO . throwIO $ WalletException $
        unwords [ "Invalid value", show n ]
    | not (isCompleteAccount acc) =
        liftIO . throwIO $ WalletException $ unwords
            [ "Keys are still missing from the incomplete account"
            , unpack $ keyRingAccountName acc
            ]
    | otherwise = do
        now <- liftIO getCurrentTime
        -- Find the next derivation index from the last address
        lastRes <- select $ from $ \x -> do
            where_ (   x ^. KeyRingAddrAccount ==. val ai
                   &&. x ^. KeyRingAddrType    ==. val addrType
                   )
            return $ max_ (x ^. KeyRingAddrIndex)
        let nextI = case lastRes of
                (Value (Just lastI):_) -> lastI + 1
                _ -> 0
            build (addr, keyM, rdmM, i) = KeyRingAddr
                { keyRingAddrAccount = ai
                , keyRingAddrAddress = addr
                , keyRingAddrIndex   = i
                , keyRingAddrType    = addrType
                , keyRingAddrLabel   = ""
                -- Full derivation from the master key
                , keyRingAddrFullDerivation =
                    let f d = toMixed d :/ branchType :/ i
                    in  f <$> keyRingAccountDerivation acc
                -- Partial derivation under the account derivation
                , keyRingAddrDerivation = Deriv :/ branchType :/ i
                , keyRingAddrRedeem     = rdmM
                , keyRingAddrKey        = keyM
                , keyRingAddrCreated    = now
                }
            res = map build $ take (fromIntegral n) $ deriveFrom nextI

        -- Save the addresses and increment the bloom filter
        insertMany_ res
        incrementFilter res
        return res
  where
    -- Branch type (external = 0, internal = 1)
    branchType = addrTypeIndex addrType
    deriveFrom = case keyRingAccountType acc of
        AccountMultisig _ m _ ->
            let f (a, r, i) = (a, Nothing, Just r, i)
                deriv  = Deriv :/ branchType
            in  map f . derivePathMSAddrs (keyRingAccountKeys acc) deriv m
        AccountRegular _ -> case keyRingAccountKeys acc of
            (key:_) -> let f (a, k, i) = (a, Just k, Nothing, i)
                       in  map f . derivePathAddrs key (Deriv :/ branchType)
            [] -> throw $ WalletException $ unwords
                [ "createAddrs: No key available in regular account"
                , unpack $ keyRingAccountName acc
                ]

-- | Generate all the addresses up to a certain index
generateAddrs :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
              => Entity KeyRingAccount
              -> AddressType
              -> KeyIndex
              -> SqlPersistT m Int
generateAddrs accE@(Entity _ _) addrType genIndex = do
    cnt <- addressCount accE addrType
    let toGen = (fromIntegral genIndex) - (fromIntegral cnt) + 1
    if toGen > 0
        then do
            _ <- createAddrs accE addrType $ fromIntegral toGen
            return toGen
        else return 0

-- | Use an address and make sure we have enough gap addresses after it.
-- Returns the new addresses that have been created.
useAddress :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
           => KeyRingAddr -> SqlPersistT m [KeyRingAddr]
useAddress KeyRingAddr{..} = do
    res <- select $ from $ \x -> do
        where_ (   x ^. KeyRingAddrAccount ==. val keyRingAddrAccount
               &&. x ^. KeyRingAddrType  ==. val keyRingAddrType
               &&. x ^. KeyRingAddrIndex >.  val keyRingAddrIndex
               )
        return countRows
    case res of
        ((Value cnt):_) -> get keyRingAddrAccount >>= \accM -> case accM of
            Just acc -> do
                let accE    = Entity keyRingAddrAccount acc
                    gap     = fromIntegral (keyRingAccountGap acc) :: Int
                    missing = 2*gap - cnt
                if missing > 0
                    then createAddrs accE keyRingAddrType $ fromIntegral missing
                    else return []
            _ -> return [] -- Should not happen
        _ -> return [] -- Should not happen

-- | Set the address gap of an account to a new value. This will create new
-- internal and external addresses as required. The gap can only be increased,
-- not decreased in size.
setAccountGap :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
              => Entity KeyRingAccount -- ^ Account Entity
              -> Word32                -- ^ New gap value
              -> SqlPersistT m KeyRingAccount
setAccountGap accE@(Entity ai acc) gap
    | not (isCompleteAccount acc) =
        liftIO . throwIO $ WalletException $ unwords
            [ "Keys are still missing from the incomplete account"
            , unpack $ keyRingAccountName acc
            ]
    | missing <= 0 = liftIO . throwIO $ WalletException
        "The gap of an account can only be increased"
    | otherwise = do
        _ <- createAddrs accE AddressExternal $ fromInteger $ missing*2
        _ <- createAddrs accE AddressInternal $ fromInteger $ missing*2
        P.update ai [ KeyRingAccountGap P.=. gap ]
        return $ acc{ keyRingAccountGap = gap }
  where
    missing = toInteger gap - toInteger (keyRingAccountGap acc)

-- Return the creation time of the first address in the wallet.
firstAddrTime :: MonadIO m => SqlPersistT m (Maybe Timestamp)
firstAddrTime = do
    res <- select $ from $ \x -> do
        orderBy [ asc (x ^. KeyRingAddrId) ]
        limit 1
        return $ x ^. KeyRingAddrCreated
    return $ case res of
        (Value d:_) -> Just $ toPOSIX d
        _ -> Nothing
  where
    toPOSIX = fromInteger . round . utcTimeToPOSIXSeconds

{- Bloom filters -}

-- | Add the given addresses to the bloom filter. If the number of elements
-- becomes too large, a new bloom filter is computed from scratch.
incrementFilter :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
                => [KeyRingAddr]
                -> SqlPersistT m ()
incrementFilter addrs = do
    (bloom, elems, _) <- getBloomFilter
    let newElems = elems + (length addrs * 2)
    if filterLen newElems > filterLen elems
        then computeNewFilter
        else setBloomFilter (addToFilter bloom addrs) newElems

-- | Generate a new bloom filter from the data in the database
computeNewFilter :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)
                 => SqlPersistT m ()
computeNewFilter = do
    (_, _, fpRate) <- getBloomFilter
    -- Create a new empty bloom filter
    -- TODO: Choose a random nonce for the bloom filter
    -- TODO: Check global bloom filter length limits
    cntRes <- select $ from $ \x -> return $ count $ x ^. KeyRingAddrId
    let elems = maybe 0 unValue $ listToMaybe cntRes
        newBloom = bloomCreate (filterLen elems) fpRate 0 BloomUpdateNone
    bloom <- addressSourceAll $$ bloomSink newBloom
    setBloomFilter bloom elems
  where
    bloomSink bloom = await >>= \addrM -> case addrM of
        Just addr -> bloomSink $ addToFilter bloom [addr]
        _         -> return bloom

-- Compute the size of a filter given a number of elements. Scale
-- the filter length by powers of 2.
filterLen :: Int -> Int
filterLen = round . pow2 . ceiling . log2
  where
    pow2 x = (2 :: Double) ** fromInteger x
    log2 x = logBase (2 :: Double) (fromIntegral x)

-- | Add elements to a bloom filter
addToFilter :: BloomFilter -> [KeyRingAddr] -> BloomFilter
addToFilter bloom addrs =
    bloom3
  where
    pks  = mapMaybe keyRingAddrKey addrs
    rdms = mapMaybe keyRingAddrRedeem addrs
    -- Add the Hash160 of the addresses
    f1 b a  = bloomInsert b $ encode' $ getAddrHash a
    bloom1 = foldl f1 bloom $ map keyRingAddrAddress addrs
    -- Add the redeem scripts
    f2 b r  = bloomInsert b $ encodeOutputBS r
    bloom2 = foldl f2 bloom1 rdms
    -- Add the public keys
    f3 b p  = bloomInsert b $ encode' p
    bloom3 = foldl f3 bloom2 pks

-- | Returns a bloom filter containing all the addresses in this wallet. This
-- includes internal and external addresses. The bloom filter can be set on a
-- peer connection to filter the transactions received by that peer.
getBloomFilter :: MonadIO m => SqlPersistT m (BloomFilter, Int, Double)
getBloomFilter = do
    res <- select $ from $ \c -> do
        limit 1
        return ( c ^. KeyRingConfigBloomFilter
               , c ^. KeyRingConfigBloomElems
               , c ^. KeyRingConfigBloomFp
               )
    case res of
        ((Value b, Value n, Value fp):_) -> return (b, n, fp)
        _ -> liftIO . throwIO $
            WalletException "getBloomFilter: Database not initialized"

-- | Save a bloom filter and the number of elements it contains
setBloomFilter :: MonadIO m => BloomFilter -> Int -> SqlPersistT m ()
setBloomFilter bloom elems =
    P.updateWhere [] [ KeyRingConfigBloomFilter P.=. bloom
                     , KeyRingConfigBloomElems  P.=. elems
                     ]

-- Helper function to compute the redeem script of a given derivation path
-- for a given multisig account.
getPathRedeem :: KeyRingAccount -> SoftPath -> RedeemScript
getPathRedeem acc@KeyRingAccount{..} deriv = case keyRingAccountType of
    AccountMultisig _ m _ -> if isCompleteAccount acc
        then sortMulSig $ PayMulSig pubKeys m
        else throw $ WalletException $ unwords
            [ "getPathRedeem: Incomplete multisig account"
            , unpack keyRingAccountName
            ]
    _ -> throw $ WalletException $ unwords
        [ "getPathRedeem: Account", unpack keyRingAccountName
        , "is not a multisig account"
        ]
  where
    f       = toPubKeyG . xPubKey . derivePubPath deriv
    pubKeys = map f keyRingAccountKeys

-- Helper function to compute the public key of a given derivation path for
-- a given non-multisig account.
getPathPubKey :: KeyRingAccount -> SoftPath -> PubKeyC
getPathPubKey acc@KeyRingAccount{..} deriv
    | isMultisigAccount acc = throw $ WalletException $
        unwords [ "getPathPubKey: Account", unpack keyRingAccountName
                , "is not a regular non-multisig account"
                ]
    | otherwise = case keyRingAccountKeys of
        (key:_) -> xPubKey $ derivePubPath deriv key
        _ -> throw $ WalletException $ unwords
            [ "getPathPubKey: No keys are available in account"
            , unpack keyRingAccountName
            ]

{- Helpers -}

subSelectAddrCount :: Entity KeyRingAccount
                   -> AddressType
                   -> SqlExpr (Value KeyIndex)
subSelectAddrCount (Entity ai acc) addrType =
    sub_select $ from $ \x -> do
        where_ (   x ^. KeyRingAddrAccount ==. val ai
               &&. x ^. KeyRingAddrType    ==. val addrType
               )
        let gap = val $ keyRingAccountGap acc
        return $ case_
            [ when_ (countRows >. gap)
              then_ (countRows -. gap)
            ] (else_ $ val 0)

validMultisigParams :: Int -> Int -> Bool
validMultisigParams m n = n >= 1 && n <= 15 && m >= 1 && m <= n

validAccountType :: AccountType -> Bool
validAccountType t = case t of
    AccountRegular _      -> True
    AccountMultisig _ m n -> validMultisigParams m n

isMultisigAccount :: KeyRingAccount -> Bool
isMultisigAccount acc = case keyRingAccountType acc of
    AccountRegular _      -> False
    AccountMultisig _ _ _ -> True

isReadAccount :: KeyRingAccount -> Bool
isReadAccount acc = case keyRingAccountType acc of
    AccountRegular r      -> r
    AccountMultisig r _ _ -> r

isCompleteAccount :: KeyRingAccount -> Bool
isCompleteAccount acc = case keyRingAccountType acc of
    AccountRegular _      -> length (keyRingAccountKeys acc) == 1
    AccountMultisig _ _ n -> length (keyRingAccountKeys acc) == n

