{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies    #-}

-- | Access to all secrets in the system. It's needed mostly for
-- testing. Not for production.

module Pos.AllSecrets
       ( InvSecretsMap
       , unInvSecretsMap
       , mkInvSecretsMap
       , InvAddrSpendingData
       , unInvAddrSpendingData
       , mkInvAddrSpendingData
       , AllSecrets (..)
       , HasAllSecrets (..)
       , mkAllSecretsSimple
       ) where

import           Universum

import           Control.Lens.TH (makeClassy)
import qualified Data.HashMap.Strict as HM
import           Formatting (bprint, int, (%))
import qualified Formatting.Buildable
import           Serokell.Util (listJson, mapJson)

import           Pos.Core (AddrSpendingData (..), Address,
                     IsBootstrapEraAddr (..), StakeholderId, addressHash,
                     checkAddrSpendingData, makePubKeyAddress,
                     makePubKeyAddressBoot)
import           Pos.Core.NetworkMagic (NetworkMagic (..))
import           Pos.Crypto (PublicKey, SecretKey, toPublic)

-- | This map effectively provides inverse of 'hash' and
-- 'toPublic' functions. It's quite useful in tests and block
-- generator. (/Inv/ means /inverse/).
newtype InvSecretsMap = InvSecretsMap
    { unInvSecretsMap :: HashMap StakeholderId SecretKey
    } deriving (Semigroup, Monoid, Container)

-- | Make 'InvSecretsMap' from a list of secret keys.
mkInvSecretsMap :: [SecretKey] -> InvSecretsMap
mkInvSecretsMap =
    let toSecretPair sk = (addressHash (toPublic sk), sk)
    in InvSecretsMap . HM.fromList . map toSecretPair

-- | This map allows to get 'AddrSpendingData' corresponding to an
-- 'Address'.
newtype InvAddrSpendingData = InvAddrSpendingData
    { unInvAddrSpendingData :: HashMap Address AddrSpendingData
    } deriving (Semigroup, Monoid, Container)

-- | Safe constructor of 'InvAddrSpendingData'.
mkInvAddrSpendingData :: [(Address, AddrSpendingData)] -> InvAddrSpendingData
mkInvAddrSpendingData pairs
    | check pairs = InvAddrSpendingData (HM.fromList pairs)
    | otherwise = error "mkInvAddrSpendingData: check failed"
  where
    check = all checkPair
    checkPair :: (Address, AddrSpendingData) -> Bool
    checkPair (addr, asd) = checkAddrSpendingData asd addr

-- | All secrets in the system.
--
-- TODO: probably VSS keys should be added here at some point.
data AllSecrets = AllSecrets
    { _asSecretKeys   :: !InvSecretsMap
    -- ^ Secret keys of all stakeholders participating in the system.
    , _asSpendingData :: !InvAddrSpendingData
    -- ^ Inverse of 'addressHash @AddrSpendingData'.
    }

makeClassy ''AllSecrets

instance Buildable AllSecrets where
    build AllSecrets {..} =
        bprint ("AllSecrets {\n"%
                "  secret keys: "%int%" items\n"%
                "  stakeholders: "%listJson%"\n"%
                "  inverse spending data: "%mapJson%"\n"%
                "}\n")
            (length _asSecretKeys)
            (HM.keys $ unInvSecretsMap _asSecretKeys)
            (unInvAddrSpendingData _asSpendingData)

-- | Make simple 'AllSecrets' assuming that only public key addresses
-- with bootstrap era distribution and single key distribution exist
-- in the system.
mkAllSecretsSimple :: NetworkMagic -> [SecretKey] -> AllSecrets
mkAllSecretsSimple nm sks =
    AllSecrets
    { _asSecretKeys = mkInvSecretsMap sks
    , _asSpendingData = invAddrSpendingData
    }
  where
    pks :: [PublicKey]
    pks = map toPublic sks
    spendingDataList = map PubKeyASD pks
    addressesNonBoot = map (makePubKeyAddress nm (IsBootstrapEraAddr False)) pks
    addressesBoot = map (makePubKeyAddressBoot nm) pks
    invAddrSpendingData =
        mkInvAddrSpendingData $
        zip addressesNonBoot spendingDataList <>
        zip addressesBoot spendingDataList
