content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  #if GHCJS_FAST_WEAK
  {-# LANGUAGE ForeignFunctionInterface #-}
  {-# LANGUAGE JavaScriptFFI #-}
  #endif
  
  -- |
  -- Module:
  --   Reflex.FastWeak
  -- Description:
  --   'FastWeak' is a weak pointer to some value, and 'FastWeakTicket' ensures the value
  --   referred to by a 'FastWeak' stays live while the ticket is held (live).
  --
  --   On GHC or GHCJS when not built with the @fast-weak@ cabal flag, 'FastWeak' is a wrapper
  --   around the simple version of 'System.Mem.Weak.Weak' where the key and value are the same.
  --
  --   On GHCJS when built with the @fast-weak@ cabal flag, 'FastWeak' is implemented directly
  --   in JS using @h$FastWeak@ and @h$FastWeakTicket@ which are a nonstandard part of the GHCJS RTS.
  module Reflex.FastWeak
    ( FastWeakTicket
    , FastWeak
    , mkFastWeakTicket
    , getFastWeakTicketValue
    , getFastWeakTicketWeak
    , getFastWeakValue
    , getFastWeakTicket
    , emptyFastWeak
  #ifdef GHCJS_FAST_WEAK
    --TODO: Move these elsewhere
    , unsafeFromRawJSVal
    , unsafeToRawJSVal
    , js_isNull
  #endif
    ) where
  
  import GHC.Exts (Any)
  import Unsafe.Coerce
  
  #ifdef GHCJS_FAST_WEAK
  import GHCJS.Types
  #else
  import Control.Exception (evaluate)
  import System.IO.Unsafe
  import System.Mem.Weak
  #endif
  
  
  #ifdef GHCJS_FAST_WEAK
  -- | A 'FastWeak' which has been promoted to a strong reference. 'getFastWeakTicketValue'
  -- can be used to get the referred to value without fear of @Nothing,
  -- and 'getFastWeakTicketWeak' can be used to get the weak version.
  --
  -- Implemented by way of special support in the GHCJS RTS, @h$FastWeakTicket@.
  newtype FastWeakTicket a = FastWeakTicket JSVal
  
  -- | A reference to some value which can be garbage collected if there are only
  -- weak references to the value left.
  --
  -- 'getFastWeakValue' can be used to try and obtain a strong reference to the value.
  --
  -- The value in a @FastWeak@ can also be kept alive by obtaining a 'FastWeakTicket' using
  -- 'getFastWeakTicket' if the value hasn't been collected yet.
  --
  -- Implemented by way of special support in the GHCJS RTS, @h$FastWeak@.
  newtype FastWeak a = FastWeak JSVal
  
  -- Just designed to mirror JSVal, so that we can unsafeCoerce between the two
  data Val a = Val { unVal :: a }
  
  -- | Coerce a JSVal that represents the heap object of a value of type @a@ into a value of type @a@
  unsafeFromRawJSVal :: JSVal -> a
  unsafeFromRawJSVal v = unVal (unsafeCoerce v)
  
  -- | Coerce a heap object of type @a@ into a 'JSVal' which represents that object.
  unsafeToRawJSVal :: a -> JSVal
  unsafeToRawJSVal v = unsafeCoerce (Val v)
  #else
  -- | A 'FastWeak' which has been promoted to a strong reference. 'getFastWeakTicketValue'
  -- can be used to get the referred to value without fear of @Nothing@,
  -- and 'getFastWeakTicketWeak' can be used to get the weak version.
  data FastWeakTicket a = FastWeakTicket
    { _fastWeakTicket_val :: !a
    , _fastWeakTicket_weak :: {-# UNPACK #-} !(Weak a)
    }
  
  -- | A reference to some value which can be garbage collected if there are only weak references to the value left.
  --
  -- 'getFastWeakValue' can be used to try and obtain a strong reference to the value.
  --
  -- The value in a @FastWeak@ can also be kept alive by obtaining a 'FastWeakTicket' using 'getFastWeakTicket'
  -- if the value hasn't been collected yet.
  --
  -- Synonymous with 'Weak'.
  type FastWeak a = Weak a
  #endif
  
  -- | Return the @a@ kept alive by the given 'FastWeakTicket'.
  --
  -- This needs to be in IO so we know that we've relinquished the ticket.
  getFastWeakTicketValue :: FastWeakTicket a -> IO a
  #ifdef GHCJS_FAST_WEAK
  getFastWeakTicketValue t = do
    v <- js_ticketVal t
    return $ unsafeFromRawJSVal v
  
  foreign import javascript unsafe "$r = $1.val;" js_ticketVal :: FastWeakTicket a -> IO JSVal
  #else
  getFastWeakTicketValue = return . _fastWeakTicket_val
  #endif
  
  -- | Get the value referred to by a 'FastWeak' if it hasn't yet been collected,
  -- or @Nothing@ if it has been collected.
  getFastWeakValue :: FastWeak a -> IO (Maybe a)
  #ifdef GHCJS_FAST_WEAK
  getFastWeakValue w = do
    r <- js_weakVal w
    case js_isNull r of
      True -> return Nothing
      False -> return $ Just $ unsafeFromRawJSVal r
  
  foreign import javascript unsafe "$1 === null" js_isNull :: JSVal -> Bool
  
  foreign import javascript unsafe "$r = ($1.ticket === null) ? null : $1.ticket.val;" js_weakVal :: FastWeak a -> IO JSVal
  #else
  getFastWeakValue = deRefWeak
  #endif
  
  -- | Try to create a 'FastWeakTicket' for the given 'FastWeak' which will ensure the value referred
  -- remains alive. Returns @Just@ if the value hasn't been collected
  -- and a ticket can therefore be obtained, @Nothing@ if it's been collected.
  getFastWeakTicket :: forall a. FastWeak a -> IO (Maybe (FastWeakTicket a))
  #ifdef GHCJS_FAST_WEAK
  getFastWeakTicket w = do
    r <- js_weakTicket w
    case js_isNull r of
      True -> return Nothing
      False -> return $ Just $ FastWeakTicket r
  
  foreign import javascript unsafe "$r = $1.ticket;" js_weakTicket :: FastWeak a -> IO JSVal
  #else
  getFastWeakTicket w = do
    deRefWeak w >>= \case
      Nothing -> return Nothing
      Just v -> return $ Just $ FastWeakTicket
        { _fastWeakTicket_val = v
        , _fastWeakTicket_weak = w
        }
  #endif
  
  -- | Create a 'FastWeakTicket' directly from a value, creating a 'FastWeak' in the process
  -- which can be obtained with 'getFastWeakTicketValue'.
  --
  -- This function is marked NOINLINE so it is opaque to GHC.
  -- If we do not do this, then GHC will sometimes fuse the constructor away
  -- so any weak references that are attached to the ticket will have their
  -- finalizer run. Using the opaque constructor, GHC does not see the
  -- constructor application, so it behaves like an IORef and cannot be fused away.
  --
  -- The result is also evaluated to WHNF, since forcing a thunk invalidates
  -- the weak pointer to it in some cases.
  {-# NOINLINE mkFastWeakTicket #-}
  mkFastWeakTicket :: a -> IO (FastWeakTicket a)
  -- I think it's fine if this is lazy - it'll retain the 'a', but so would the output; we just need to make sure it's forced before we start relying on the
  -- associated FastWeak to actually be weak
  #ifdef GHCJS_FAST_WEAK
  mkFastWeakTicket v = js_fastWeakTicket (unsafeToRawJSVal v)
  
  foreign import javascript unsafe "$r = new h$FastWeakTicket($1);" js_fastWeakTicket :: JSVal -> IO (FastWeakTicket a)
  #else
  mkFastWeakTicket v = do
    v' <- evaluate v
    w <- mkWeakPtr v' Nothing
    return $ FastWeakTicket
      { _fastWeakTicket_val = v'
      , _fastWeakTicket_weak = w
      }
  #endif
  
  -- | Demote a 'FastWeakTicket'; which ensures the value is alive, to a 'FastWeak' which doesn't.
  -- Note that unless the ticket for the same 'FastWeak' is held in some other way
  -- the value might be collected immediately.
  getFastWeakTicketWeak :: FastWeakTicket a -> IO (FastWeak a)
  -- Needs IO so that it can force the value - otherwise, could end up with a reference to the Ticket, which would retain the value
  #ifdef GHCJS_FAST_WEAK
  foreign import javascript unsafe "$r = $1.weak;" getFastWeakTicketWeak' :: FastWeakTicket a -> IO (FastWeak a)
  {-# INLINE getFastWeakTicketWeak #-}
  getFastWeakTicketWeak = getFastWeakTicketWeak'
  #else
  getFastWeakTicketWeak = return . _fastWeakTicket_weak
  #endif
  
  -- | A weak reference that is always empty
  emptyFastWeak :: FastWeak a
  emptyFastWeak = unsafeCoerce w
    where w :: FastWeak Any
  #ifdef GHCJS_FAST_WEAK
          w = js_emptyWeak
  #else
          w = unsafePerformIO $ do
            w' <- mkWeakPtr undefined Nothing
            finalize w'
            return w'
  #endif
  {-# NOINLINE emptyFastWeak #-}
  
  #ifdef GHCJS_FAST_WEAK
  foreign import javascript unsafe "$r = new h$FastWeak(null);" js_emptyWeak :: FastWeak Any
  #endif
  