module Data.CQRS.Types.SnapshotStore
    ( SnapshotStore(..)
    , applyPrism
    , nullSnapshotStore
    ) where

import Data.CQRS.Types.Snapshot (Snapshot(..))
import Data.UUID.Types (UUID)

-- | A snapshot store is used for storing snapshots.
data SnapshotStore a = SnapshotStore
    {
      -- | Write out a snapshot. Snapshot version numbers are NOT
      -- checked for validity (e.g. whether they are greater than the
      -- existing snapshot version numbers).
      ssWriteSnapshot :: UUID -> Snapshot a -> IO ()
    ,
      -- | Read latest snapshot of an aggregate identified by UUID. A
      -- snapshot store is permitted to return 'Nothing' in all cases.
      ssReadSnapshot :: UUID -> IO (Maybe (Snapshot a))
    }

-- | Transform an implementation of 'SnapshotStore a' to an
-- implementation of 'SnapshotStore b' via a prism. This is typically
-- used to add serialization/deserialization to snapshot stores which
-- do not support storing anything other than binary data. Note that
-- the prism is allowed to return 'Nothing' to indicate that data could
-- not be transformed. This can be used to avoid the need for complicated
-- versioning of snapshot data by using simple version tags/hashes to
-- determine compatibility with stored snapshots.
applyPrism :: (a -> b, b -> Maybe a) -> SnapshotStore b -> SnapshotStore a
applyPrism (f, g) (SnapshotStore writeSnapshot' readSnapshot') =
    SnapshotStore writeSnapshot readSnapshot
    where
      writeSnapshot aggregateId (Snapshot v a) = do
          writeSnapshot' aggregateId $ Snapshot v $ f a
      readSnapshot aggregateId = do
          fmap (f' =<<) $ (readSnapshot' aggregateId)
          where
            f' (Snapshot v a) = g a >>= Just . Snapshot v

-- | The "null" snapshot store, i.e. a snapshot store which never
-- actually stores any snapshots.
nullSnapshotStore :: SnapshotStore a
nullSnapshotStore =
    SnapshotStore { ssWriteSnapshot = \_ _ -> return ()
                  , ssReadSnapshot = \_ -> return Nothing
                  }