{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-missing-methods #-}

-- | 'JSChan' provides the same functionality and
--   concurrency abstraction in Javascript computations
--   as 'Control.Concurrent.Chan' in Haskell.
module Language.Sunroof.JS.Chan
  ( JSChan
  , newChan
  , writeChan, readChan
  ) where

import Data.Boolean ( IfB(..), EqB(..) )

import Language.Sunroof.Classes
  ( Sunroof(..), SunroofArgument(..) )
import Language.Sunroof.Types
import Language.Sunroof.Concurrent ( forkJS )
import Language.Sunroof.Selector ( (!) )
import Language.Sunroof.TH
import Language.Sunroof.JS.Object ( JSObject )
import Language.Sunroof.JS.Array
  ( JSArray
  , newArray, length'
  , push, shift )

-- -------------------------------------------------------------
-- JSChan Type
-- -------------------------------------------------------------

-- | 'JSChan' abstraction. The type parameter gives
--   the type of values held in the channel.
newtype JSChan a = JSChan JSObject

deriveJSTuple
  [d| instance (SunroofArgument o) => JSTuple (JSChan o) where
          type Internals (JSChan o) =
                  ( (JSArray (JSContinuation (JSContinuation o))) -- callbacks of written data
                  , (JSArray (JSContinuation o))                 -- callbacks of waiting readers
                  )
  |]

-- | Reference equality, not value equality.
instance (SunroofArgument o) => EqB (JSChan o) where
  (JSChan a) ==* (JSChan b) = a ==* b

-- -------------------------------------------------------------
-- JSChan Combinators
-- -------------------------------------------------------------

-- | Create a new empty 'JSChan'.
newChan :: (SunroofArgument a) => JS t (JSChan a)
newChan = do
  written <- newArray ()
  waiting <- newArray ()
  tuple (written, waiting)

-- | Put a value into the channel. This will never block.
writeChan :: forall t a . (SunroofThread t, SunroofArgument a) => a -> JSChan a -> JS t ()
writeChan a (match -> (written,waiting)) = do
  ifB ((waiting ! length') ==* 0)
      (do f <- continuation $ \ (k :: JSContinuation a) -> goto k a :: JSB ()
          written # push (f :: JSContinuation (JSContinuation a))
      )
      (do f <- shift waiting
          forkJS (goto f a :: JSB ())
      )

-- | Take a value out of the channel. If there is no value
--   inside, this will block until one is available.
readChan :: forall a . (Sunroof a, SunroofArgument a) => JSChan a -> JS B a
readChan (match -> (written,waiting)) = do
  ifB ((written ! length') ==* 0)
      (do -- Add yourself to the 'waiting for writer' Q.
          callcc $ \ k -> do waiting # push (k :: JSContinuation a)
                             done
      )
      (do f <- shift written
          -- Here, we add our continuation into the written Q.
          callcc $ \ k -> goto f k
      )