{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes            #-}

module Data.Morpheus.Interpreter
  ( Interpreter(..)
  ) where

import           Data.Aeson                             (encode)
import           Data.ByteString                        (ByteString)
import qualified Data.ByteString.Lazy.Char8             as LB (ByteString, fromStrict, toStrict)
import           Data.Morpheus.Resolve.Operator         (RootResCon)
import           Data.Morpheus.Resolve.Resolve          (packStream, resolve, resolveByteString, resolveStream,
                                                         resolveStreamByteString)
import           Data.Morpheus.Server.ClientRegister    (GQLState)
import           Data.Morpheus.Types.Internal.WebSocket (OutputAction)
import           Data.Morpheus.Types.IO                 (GQLRequest, GQLResponse)
import           Data.Morpheus.Types.Resolver           (GQLRootResolver (..))
import           Data.Text                              (Text)
import qualified Data.Text.Lazy                         as LT (Text, fromStrict, toStrict)
import           Data.Text.Lazy.Encoding                (decodeUtf8, encodeUtf8)

-- | main query processor and resolver
--  possible versions of interpreter
--
-- 1. with effect and state: where 'GQLState' is State Monad of subscriptions
--
--     @
--      k :: GQLState -> a -> IO a
--     @
--
-- 2. without effect and state: stateless query processor without any effect,
--    if you don't need any subscription use this one , is simple and fast
--
--     @
--       k :: a -> IO a
--       -- or
--       k :: GQLRequest -> IO GQLResponse
--     @
class Interpreter k m where
  interpreter ::
       Monad m
    => (RootResCon m a b c) =>
         GQLRootResolver m a b c -> k

{-
  simple HTTP stateless Interpreter without side effects
-}
type StateLess m a = a -> m a

instance Interpreter (GQLRequest -> m GQLResponse) m where
  interpreter = resolve

instance Interpreter (StateLess m LB.ByteString) m where
  interpreter = resolveByteString

instance Interpreter (StateLess m LT.Text) m where
  interpreter root request = decodeUtf8 <$> interpreter root (encodeUtf8 request)

instance Interpreter (StateLess m ByteString) m where
  interpreter root request = LB.toStrict <$> interpreter root (LB.fromStrict request)

instance Interpreter (StateLess m Text) m where
  interpreter root request = LT.toStrict <$> interpreter root (LT.fromStrict request)

{-
   HTTP Interpreter with state and side effects, every mutation will
   trigger subscriptions in  shared `GQLState`
-}
type WSPub m a = GQLState -> a -> m a

instance Interpreter (WSPub IO LB.ByteString) IO where
  interpreter root state = packStream state (resolveStreamByteString root)

instance Interpreter (WSPub IO LT.Text) IO where
  interpreter root state request = decodeUtf8 <$> interpreter root state (encodeUtf8 request)

instance Interpreter (WSPub IO ByteString) IO where
  interpreter root state request = LB.toStrict <$> interpreter root state (LB.fromStrict request)

instance Interpreter (WSPub IO Text) IO where
  interpreter root state request = LT.toStrict <$> interpreter root state (LT.fromStrict request)

{-
   Websocket Interpreter without state and side effects, mutations and subscription will return Actions
   that will be executed in Websocket server
-}
type WSSub m a = a -> m (OutputAction m a)

instance Interpreter (GQLRequest -> m (OutputAction m LB.ByteString)) m where
  interpreter root request = fmap encode <$> resolveStream root request

instance Interpreter (WSSub m LB.ByteString) m where
  interpreter = resolveStreamByteString

instance Interpreter (WSSub m LT.Text) m where
  interpreter root request = fmap decodeUtf8 <$> interpreter root (encodeUtf8 request)

instance Interpreter (WSSub m ByteString) m where
  interpreter root request = fmap LB.toStrict <$> interpreter root (LB.fromStrict request)

instance Interpreter (WSSub m Text) m where
  interpreter root request = fmap LT.toStrict <$> interpreter root (LT.fromStrict request)