{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings, BangPatterns, CPP #-}
{-# LANGUAGE TypeFamilies, FlexibleInstances, UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
#if __GLASGOW_HASKELL__ < 710
{-# LANGUAGE OverlappingInstances #-}
#endif
-- | High level JavaScript foreign interface.
module Haste.Prim.Foreign (
    module Haste.Prim.Any,
    FFI, JSFunc,
    ffi, constant, export
#if __GLASGOW_HASKELL__ >= 710
    , safe_ffi, StaticPtr
#endif
  ) where
import Haste.Prim
import Haste.Prim.Any
#if __GLASGOW_HASKELL__ >= 710
import GHC.StaticPtr (StaticPtr, deRefStaticPtr)
#endif

-- | A JS function.
type JSFun = JSAny

#ifdef __HASTE__
foreign import ccall "eval" __eval :: JSString -> JSFun
foreign import ccall __apply :: JSFun -> Ptr [JSAny] -> IO JSAny
foreign import ccall __app0  :: JSFun -> IO JSAny
foreign import ccall __app1  :: JSFun -> JSAny -> IO JSAny
foreign import ccall __app2  :: JSFun -> JSAny -> JSAny -> IO JSAny
foreign import ccall __app3  :: JSFun -> JSAny -> JSAny -> JSAny -> IO JSAny
foreign import ccall __app4  :: JSFun
                             -> JSAny -> JSAny -> JSAny -> JSAny -> IO JSAny
foreign import ccall __app5  :: JSFun
                             -> JSAny -> JSAny -> JSAny -> JSAny -> JSAny
                             -> IO JSAny
foreign import ccall __createJSFunc :: Int -> JSAny -> IO JSAny
#else
__eval :: JSString -> JSFun
__eval _ = undefined
__apply :: JSFun -> Ptr [JSAny] -> IO JSAny
__apply _ _ = return undefined
__app0  :: JSFun -> IO JSAny
__app0 _ = return undefined
__app1  :: JSFun -> JSAny -> IO JSAny
__app1 _ _ = return undefined
__app2  :: JSFun -> JSAny -> JSAny -> IO JSAny
__app2 _ _ _ = return undefined
__app3  :: JSFun -> JSAny -> JSAny -> JSAny -> IO JSAny
__app3 _ _ _ _ = return undefined
__app4  :: JSFun -> JSAny -> JSAny -> JSAny -> JSAny -> IO JSAny
__app4 _ _ _ _ _ = return undefined
__app5  :: JSFun -> JSAny -> JSAny -> JSAny -> JSAny -> JSAny -> IO JSAny
__app5 _ _ _ _ _ _ = return undefined
__createJSFunc :: Int -> JSAny -> IO JSAny
__createJSFunc _ = return undefined
#endif

-- | Any type that can be imported from JavaScript. This means any type which
--   has an instance of 'FromAny', and any function where all argument types
--   has 'ToAny' instances and the return type is in the IO monad and has a
--   'FromAny' instance.
class FFI a where
  __ffi :: JSFun -> [JSAny] -> a

instance FromAny a => FFI (IO a) where
  {-# INLINE __ffi #-}
  __ffi = ffiio

instance (ToAny a, FFI b) => FFI (a -> b) where
  {-# INLINE __ffi #-}
  __ffi f !as !a = __ffi f (a' : as)
    where !a' = toAny a

{-# INLINE [0] ffiio #-}
-- | Apply the result of an FFI call.
ffiio :: FromAny a => JSFun -> [JSAny] -> IO a
ffiio !f !as = __apply f (toPtr as) >>= fromAny

{-# INLINE ffi #-}
-- | Creates a Haskell function from the given string of JavaScript code. If
--   this code is not well typed or is otherwise incorrect, your program may
--   crash or misbehave in mystifying ways. Haste makes a best-effort try to
--   save you from poorly typed JS here, but there are no guarantees.
--
--   For instance, the following WILL cause crazy behavior due to wrong types:
--
--   > ffi "(function(x) {return x+1;})" :: Int -> Int -> IO Int
--
--   In other words, this function is as unsafe as the JS it calls on. You
--   have been warned.
--
--   The imported JS is evaluated lazily, unless (a) it is a function object
--   in which case evaluation order does not affect the semantics of the
--   imported code, or if (b) the imported code is explicitly marked as strict:
--
--   > someFunction = ffi "__strict(someJSFunction)"
--
--   Literals which depends on some third party initialization, the existence
--   of a DOM tree or some other condition which is not fulfilled at load time
--   should *not* be marked strict.
ffi :: FFI a => JSString -> a
ffi s = __ffi f []
  where
    {-# NOINLINE f #-}
    f = __eval s

#if __GLASGOW_HASKELL__ >= 710
safe_ffi :: FFI a => StaticPtr JSString -> a
safe_ffi = ffi . deRefStaticPtr
#endif

-- | Create a Haskell value from a constant JS expression.
constant :: FromAny a => JSString -> a
constant = veryUnsafePerformIO . fromAny . __eval

-- Don't build intermediate list for functions of <= 5 arguments.
{-# RULES
"app0" [1] forall f. ffiio f [] = __app0 f >>= fromAny
"app1" [1] forall f a. ffiio f [a] = __app1 f a >>= fromAny
"app2" [1] forall f a b. ffiio f [b,a] = __app2 f a b >>= fromAny
"app3" [1] forall f a b c. ffiio f [c,b,a] = __app3 f a b c >>= fromAny
"app4" [1] forall f a b c d. ffiio f [d,c,b,a] = __app4 f a b c d >>= fromAny
"app5" [1] forall f a b c d e. ffiio f [e,d,c,b,a] =
                                 __app5 f a b c d e >>= fromAny
  #-}

-- | Export a symbol. That symbol may then be accessed from JavaScript through
--   @Haste.name()@ as a normal function. Remember, however, that if you are
--   using --with-js to include your JS, in conjunction with
--   --opt-minify or any option that implies it, you will instead need
--   to access your exports through @Haste[\'name\']()@, or Closure will mangle
--   your function names.
{-# INLINE export #-}
export :: ToAny a => JSString -> a -> IO ()
export = ffi "(function(s,f){Haste[s] = f;})"

type family JS a where
  JS (a -> b) = JSAny -> JS b
  JS (IO a)   = IO JSAny
  JS a        = JSAny

class JSFunc a where
  mkJSFunc :: a -> JS a
  arity    :: a -> Int

#if __GLASGOW_HASKELL__ < 710
instance (ToAny a, JS a ~ JSAny) => JSFunc a where
#else
instance {-# OVERLAPPABLE #-} (ToAny a, JS a ~ JSAny) => JSFunc a where
#endif
  mkJSFunc = toAny
  arity _  = 0

instance ToAny a => JSFunc (IO a) where
  mkJSFunc = fmap toAny
  arity _  = 1

instance (FromAny a, JSFunc b) => JSFunc (a -> b) where
  mkJSFunc f = mkJSFunc . f . veryUnsafePerformIO . fromAny
  arity f    = 1 + arity (f undefined)

instance (FromAny a, JSFunc b) => ToAny (a -> b) where
  toAny f =
    veryUnsafePerformIO . __createJSFunc (arity f) . toAny . toOpaque $ mkJSFunc f

instance ToAny a => ToAny (IO a) where
  toAny = veryUnsafePerformIO . __createJSFunc 0 . toAny . toOpaque . mkJSFunc

#if __GLASGOW_HASKELL__ < 710
instance FFI a => FromAny a where
#else
instance {-# OVERLAPPABLE #-} FFI a => FromAny a where
#endif
  fromAny f = return $ __ffi f []