module Database.VCache.PVar
    ( PVar
    , newPVar
    , newPVars
    , newPVarIO
    , newPVarsIO
    , loadRootPVar
    , loadRootPVarIO
    , readPVar
    , readPVarIO
    , writePVar
    , modifyPVar
    , modifyPVar'
    , swapPVar
    , pvar_space
    , unsafePVarAddr
    , unsafePVarRefct
    ) where

import Control.Concurrent.STM

import Database.VCache.Types
import Database.VCache.Alloc ( newPVar, newPVars, newPVarIO, newPVarsIO
                             , loadRootPVar, loadRootPVarIO)
import Database.VCache.Read (readRefctIO)

-- | Read a PVar as part of a transaction.
readPVar :: PVar a -> VTx a
readPVar pvar =
    getVTxSpace >>= \ space ->
    if space /= pvar_space pvar then fail eBadSpace else
    liftSTM $ readTVar (pvar_data pvar) >>= \case { (RDV v) -> return v }
{-# INLINABLE readPVar #-}

-- Note that readPVar and readPVarIO must be strict in RDV in order to force
-- the initial, lazy read from the database. This is the only reason for RDV.
-- Without forcing here, a lazy read might return a value from an update.

-- | Read a PVar in the IO monad.
--
-- This is more efficient than a full transaction. It simply peeks at
-- the underlying TVar with readTVarIO. Durability of the value read
-- is not guaranteed.
readPVarIO :: PVar a -> IO a
readPVarIO pv =
    readTVarIO (pvar_data pv) >>= \case { (RDV v) -> return v }
{-# INLINE readPVarIO #-}

eBadSpace :: String
eBadSpace = "VTx: mismatch between VTx VSpace and PVar VSpace"

-- | Write a PVar as part of a transaction.
writePVar :: PVar a -> a -> VTx ()
writePVar pvar v =
    getVTxSpace >>= \ space ->
    if space /= pvar_space pvar then fail eBadSpace else
    markForWrite pvar v >>
    liftSTM (writeTVar (pvar_data pvar) (RDV v))
{-# INLINABLE writePVar #-}


-- | Modify a PVar.
modifyPVar :: PVar a -> (a -> a) -> VTx ()
modifyPVar var f = do
    x <- readPVar var
    writePVar var (f x)
{-# INLINE modifyPVar #-}

-- | Modify a PVar, strictly.
modifyPVar' :: PVar a -> (a -> a) -> VTx ()
modifyPVar' var f = do
    x <- readPVar var
    writePVar var $! f x
{-# INLINE modifyPVar' #-}

-- | Swap contents of a PVar for a new value.
swapPVar :: PVar a -> a -> VTx a
swapPVar var new = do
    old <- readPVar var
    writePVar var new
    return old
{-# INLINE swapPVar #-}

-- | Each PVar has a stable address in the VCache. This address will
-- be very stable, but is not deterministic and isn't really something
-- you should treat as meaningful information about the PVar. Mostly,
-- this function exists to support hashtables or memoization with
-- PVar keys.
--
-- The Show instance for PVars will also show the address.
unsafePVarAddr :: PVar a -> Address
unsafePVarAddr = pvar_addr
{-# INLINE unsafePVarAddr #-}

-- | This function allows developers to access the reference count
-- for the PVar that is currently recorded in the database. This may
-- be useful for heuristic purposes. However, caveats are needed:
--
-- First, because the VCache writer operates in a background thread,
-- the reference count returned here may be slightly out of date.
--
-- Second, it is possible that VCache will eventually use some other
-- form of garbage collection than reference counting. This function
-- should be considered an unstable element of the API.
--
-- Root PVars start with one root reference.
unsafePVarRefct :: PVar a -> IO Int
unsafePVarRefct var = readRefctIO (pvar_space var) (pvar_addr var)

