content: {-# LANGUAGE CPP, OverloadedStrings #-}
  -- | High level JavaScript foreign interface.
  module Haste.Foreign
    ( -- * Conversion to/from JSAny
      ArrView, ToAny (..), FromAny (..), JSAny
    , Opaque, toOpaque, fromOpaque
    , nullValue, toObject, has, get, index, isUndefined
    , getMaybe, hasAll, lookupAny, JSException (..)
  
      -- * Importing and exporting JavaScript functions
    , FFI, JSFunc
    , ffi, constant, export
    , safe_ffi, StaticPtr
    , withLibraries
    ) where
  import Haste.Prim.Foreign
  import Haste.Prim (JSString)
  import qualified Haste.JSString as J
  import Control.Monad (foldM)
  
  -- For withLibraries
  import Haste.Concurrent
  import Haste.DOM.JSString
  import Haste.Events
  
  -- For array instances
  import Haste.Foreign.Array (ArrView)
  
  -- | Read a member from a JS object. Succeeds if the member exists.
  getMaybe :: FromAny a => JSAny -> JSString -> IO (Maybe a)
  getMaybe a k = do exists <- has a k
                    if exists then Just <$> get a k
                      else pure Nothing
  
  -- | Checks if a JS object has a list of members. Succeeds if the JS object
  --   has every member given in the list. 
  hasAll :: JSAny -> [JSString] -> IO Bool
  hasAll a ks = and <$> mapM (has a) ks
  
  
  -- | Looks up an object by a `.`-separated string. Succeeds if the lowest
  --   member exists.
  --
  -- Usage example:
  -- 
  -- >>> {'child': {'childrer': {'childerest': "I am very much a child."}}}
  --
  -- Given the JS Object above, we can access the deeply nested object,
  --  childerest, by lookupAny as in the below example
  --
  -- >>> lookupAny jsObject "child.childrer.childerest"
  lookupAny :: JSAny -> JSString -> IO (Maybe JSAny)
  lookupAny root i = foldM hasGet (Just root) $ J.match dotsplit i
    where hasGet Nothing       _     = pure Nothing
          hasGet (Just parent) ident = do h <- has parent ident
                                          if h then Just <$> get parent ident
                                            else pure Nothing
          dotsplit = J.regex "[^.]+" "g"
  
  -- | Wait for the given libraries to load before executing the given
  --   computation. Note that this function returns immediately, BEFORE the given
  --   libraries are loaded.
  withLibraries :: [JSString] -> IO () -> IO ()
  withLibraries libs m = concurrent $ do
      vars <- mapM (const newEmptyMVar) libs
      liftIO . sequence_ $ zipWith addLib libs vars
      mapM_ takeMVar vars
      liftIO m
    where
      addLib lib var = do
        s <- newElem "SCRIPT" `with` ["src" =: lib]
        s `onEvent` Load $ \_ -> concurrent $ putMVar var ()
        appendChild documentBody s
  