{-| This module defines the underlying __unsafe__ primitives StrictCheck uses
    to implement purely functional observation of evaluation.

    The "functions" in this module are __not referentially transparent__!
-}
module Test.StrictCheck.Observe.Unsafe where

import System.IO.Unsafe
import Data.IORef

import Data.Bifunctor
import Generics.SOP (I(..), unI)

import Test.StrictCheck.Shaped
import Test.StrictCheck.Demand

-- | From some value of any type, produce a pair: a copy of the original value,
-- and a 'Thunk' of that same type, with their values determined by the
-- /order/ in which their values themselves are evaluated
--
-- If the copy of the value is evaluated to weak-head normal form before the
-- returned @Thunk@, then any future inspection of the @Thunk@ will show that it
-- is equal to the original value wrapped in an @Eval@. However, if the copy of
-- the value is /not/ evaluated by the time the @Thunk@ is evaluated, any future
-- inspection of the @Thunk@ will show that it is equal to @Thunk@.
--
-- A picture may be worth 1000 words:
--
-- >>> x = "hello," ++ " world"
-- >>> (x', t) = entangle x
-- >>> x'
-- "hello, world"
-- >>> t
-- Eval "hello, world"
--
-- >>> x = "hello," ++ " world"
-- >>> (x', t) = entangle x
-- >>> t
-- Thunk
-- >>> x'
-- "hello, world"
-- >>> t
-- Thunk
{-# NOINLINE entangle #-}
entangle :: forall a. a -> (a, Thunk a)
entangle a =
  unsafePerformIO $ do
    ref <- newIORef Thunk
    return ( unsafePerformIO $ do
               writeIORef ref (Eval a)
               return a
           , unsafePerformIO $ readIORef ref )

-- | Recursively 'entangle' an @a@, producing not merely a @Thunk@, but an
-- entire @Demand@ which is piecewise entangled with that value. Whatever
-- portion of the entangled value is evaluated before the corresponding portion
-- of the returned @Demand@ will be represented in the shape of that @Demand@.
-- However, any part of the returned @Demand@ which is evaluated before the
-- corresponding portion of the entangled value will be forever equal to
-- @Thunk@.
--
-- The behavior of this function is even more tricky to predict than that of
-- 'entangle', especially when evaluation of the entangled value and the
-- corresponding @Demand@ happen at the same time. In StrictCheck, all
-- evaluation of the entangled value occurs before any evaluation of the
-- @Demand@; we never interleave their evaluation.
{-# NOINLINE entangleShape #-}
entangleShape :: Shaped a => a -> (a, Demand a)
entangleShape =
  first (fuse unI)
  . unzipWith entangle'
  . interleave I
  where
    entangle' :: I x -> (I x, Thunk x)
    entangle' =
      first I . entangle . unI