{-# LANGUAGE CPP                        #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase                 #-}
{- |
Module      :  Neovim.RPC.EventHandler
Description :  Event handling loop
Copyright   :  (c) Sebastian Witte
License     :  Apache-2.0

Maintainer  :  woozletoff@gmail.com
Stability   :  experimental

-}
module Neovim.RPC.EventHandler (
    runEventHandler,
    ) where

import           Neovim.Classes
import           Neovim.Context
import qualified Neovim.Context.Internal      as Internal
import           Neovim.Plugin.IPC.Classes
import qualified Neovim.RPC.Classes           as MsgpackRPC
import           Neovim.RPC.Common
import           Neovim.RPC.FunctionCall

import           Control.Applicative
import           Control.Concurrent.STM       hiding (writeTQueue)
import           Control.Monad.Reader
import           Control.Monad.State.Strict
import           Control.Monad.Trans.Resource
import           Data.ByteString              (ByteString)
import           Data.Conduit                 as C
#if MIN_VERSION_conduit_extra(1,2,2)
import           Data.Conduit.Binary          (sinkHandleFlush)
#else
import           Data.Conduit.Binary          (sinkHandle)
#endif
import qualified Data.Map                     as Map
import           Data.Serialize               (encode)
import           System.IO                    (Handle)
import           System.Log.Logger

import           Prelude


-- | This function will establish a connection to the given socket and write
-- msgpack-rpc requests to it.
runEventHandler :: Handle
                -> Internal.Config RPCConfig Int64
                -> IO ()
runEventHandler writeableHandle env =
    runEventHandlerContext env $ do
        eventHandlerSource
            $= eventHandler
            $$ addCleanup
                (cleanUpHandle writeableHandle)
#if MIN_VERSION_conduit_extra(1,2,2)
                (sinkHandleFlush writeableHandle)
#else
                (sinkHandle writeableHandle)
#endif


-- | Convenient monad transformer stack for the event handler
newtype EventHandler a =
    EventHandler (ResourceT (ReaderT (Internal.Config RPCConfig Int64) (StateT Int64 IO)) a)
    deriving ( Functor, Applicative, Monad, MonadState Int64, MonadIO
             , MonadReader (Internal.Config RPCConfig Int64))


runEventHandlerContext
    :: Internal.Config RPCConfig Int64 -> EventHandler a -> IO a
runEventHandlerContext env (EventHandler a) =
    evalStateT (runReaderT (runResourceT a) env) 1


eventHandlerSource :: Source EventHandler SomeMessage
eventHandlerSource = asks Internal.eventQueue >>= \q ->
    forever $ yield =<< atomically' (readTQueue q)


eventHandler :: ConduitM SomeMessage EncodedResponse EventHandler ()
eventHandler = await >>= \case
    Nothing ->
        return () -- i.e. close the conduit -- TODO signal shutdown globally

    Just message -> do
        handleMessage (fromMessage message, fromMessage message)
        eventHandler


#if MIN_VERSION_conduit_extra(1,2,2)
type EncodedResponse = Flush ByteString
#else
type EncodedResponse = ByteString
#endif

yield' :: (MonadIO io) => MsgpackRPC.Message -> ConduitM i EncodedResponse io ()
yield' o = do
    liftIO . debugM "EventHandler" $ "Sending: " ++ show o
#if MIN_VERSION_conduit_extra(1,2,2)
    yield . Chunk . encode $ toObject o
    yield Flush
#else
    yield . encode $ toObject o
#endif

handleMessage :: (Maybe FunctionCall, Maybe MsgpackRPC.Message)
              -> ConduitM i EncodedResponse EventHandler ()
handleMessage = \case
    (Just (FunctionCall fn params reply time), _) -> do
        i <- get
        modify succ
        rs <- asks (recipients . Internal.customConfig)
        atomically' . modifyTVar rs $ Map.insert i (time, reply)
        yield' $ MsgpackRPC.Request (Request fn i params)

    (_, Just r@MsgpackRPC.Response{}) ->
        yield' $ r

    (_, Just n@MsgpackRPC.Notification{}) ->
        yield' $ n

    _ ->
        return () -- i.e. skip to next message