{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE JavaScriptFFI #-}
{-# OPTIONS_HADDOCK hide #-}

module JavaScript.WebSockets.FFI (
  -- * Types
    Socket
  , Waiter
  , ConnectionQueue
  , ConnectionWaiters
  , WaiterKilled
    -- * FFI
  , ws_newSocket
  , ws_closeSocket
  , ws_socketSend
  , ws_awaitConn
  , ws_clearWaiters
  , ws_clearQueue
  , ws_handleOpen
  , ws_handleClose
  , ws_readyState
  ) where

import Data.Text   (Text)

#ifdef ghcjs_HOST_OS
import GHCJS.Types (JSRef, JSArray, JSString, JSObject, JSBool)
#else
import JavaScript.NoGHCJS
#endif

data Socket_
type Socket = JSRef Socket_

data Waiter_
type Waiter = JSRef Waiter_

type WaiterKilled = JSObject JSBool

type ConnectionQueue = JSArray Text
type ConnectionWaiters = JSArray Waiter

type WSCloseEvent = JSObject ()

#ifdef ghcjs_HOST_OS
foreign import javascript unsafe "$1.close();"
  ws_closeSocket :: Socket -> IO ()

foreign import javascript unsafe "$1.send(atob($2));"
  ws_socketSend :: Socket -> JSString -> IO ()

foreign import javascript interruptible "$1.onopen = function() { $c($1); };"
  ws_handleOpen :: Socket -> IO Socket

foreign import javascript unsafe  "var ws = new WebSocket($1);\
                                   ws.onmessage = function(e) {\
                                     if (!(typeof e === 'undefined')) {\
                                       if (window.ws_debug) {\
                                         console.log(e);\
                                       };\
                                       $2.push(e.data);\
                                       if ($3.length > 0) {\
                                         var e0 = $2.shift();\
                                         var found = false;\
                                         while ($3.length > 0 && !found) {\
                                           var w0 = $3.shift();\
                                           found = w0(e0);\
                                         };\
                                         if (!found) {\
                                           $2.unshift(e0);\
                                         };\
                                       };\
                                     }\
                                   };\
                                   $r = ws;"
  ws_newSocket :: JSString -> ConnectionQueue -> ConnectionWaiters -> IO Socket

foreign import javascript interruptible  "if ($1.length > 0) {\
                                            var d = $1.shift();\
                                            $c(d);\
                                          } else {\
                                            $2.push(function(d) {\
                                              if ($3.k) {\
                                                return false;\
                                              } else {\
                                                $c(d);\
                                                return true;\
                                              };\
                                            });\
                                          }"
  ws_awaitConn :: ConnectionQueue -> ConnectionWaiters -> WaiterKilled -> IO (JSRef ())

foreign import javascript unsafe "while ($1.length > 0) {\
                                    var w0 = $1.shift();\
                                    w0(null);\
                                  };"
  ws_clearWaiters :: ConnectionWaiters -> IO ()

foreign import javascript unsafe "while ($1.length > 0) { $1.shift(); };"
  ws_clearQueue :: ConnectionQueue -> IO ()

foreign import javascript interruptible  "$1.onclose = function (e) { $c(e); };"
  ws_handleClose :: Socket -> IO WSCloseEvent

foreign import javascript unsafe "$1.readyState"
  ws_readyState :: Socket -> IO Int
#else

ws_closeSocket :: Socket -> IO ()
ws_socketSend :: Socket -> JSString -> IO ()
ws_handleOpen :: Socket -> IO Socket
ws_newSocket :: JSString -> ConnectionQueue -> ConnectionWaiters -> IO Socket
ws_awaitConn :: ConnectionQueue -> ConnectionWaiters -> WaiterKilled -> IO (JSRef ())
ws_clearWaiters :: ConnectionWaiters -> IO ()
ws_clearQueue :: ConnectionQueue -> IO ()
ws_handleClose :: Socket -> IO WSCloseEvent
ws_readyState :: Socket -> IO Int

ws_closeSocket = error "ws_closeSocket: only available in JavaScript"
ws_socketSend = error "ws_socketSend: only available in JavaScript"
ws_handleOpen = error "ws_handleOpen: only available in JavaScript"
ws_newSocket = error "ws_newSocket: only available in JavaScript"
ws_awaitConn = error "ws_awaitConn: only available in JavaScript"
ws_clearWaiters = error "ws_clearWaiters: only available in JavaScript"
ws_clearQueue = error "ws_clearQueue: only available in JavaScript"
ws_handleClose = error "ws_handleClose: only available in JavaScript"
ws_readyState = error "ws_readyState: only available in JavaScript"

#endif