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

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

import           Data.Aeson                                          (encode)
import           Data.ByteString                                     (ByteString)
import qualified Data.ByteString.Lazy.Char8                          as LB (ByteString, fromStrict, toStrict)
import           Data.Text                                           (Text)
import qualified Data.Text.Lazy                                      as LT (Text, fromStrict, toStrict)
import           Data.Text.Lazy.Encoding                             (decodeUtf8, encodeUtf8)

-- MORPHEUS
import           Data.Morpheus.Execution.Server.Resolve              (RootResCon, byteStringIO, statefulResolver,
                                                                      statelessResolver, streamResolver)
import           Data.Morpheus.Execution.Subscription.ClientRegister (GQLState)
import           Data.Morpheus.Types.Internal.Stream                 (ResponseStream)
import           Data.Morpheus.Types.IO                              (GQLRequest, GQLResponse)
import           Data.Morpheus.Types.Resolver                        (GQLRootResolver (..))

-- | 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 e c where
  interpreter ::
       Monad m
    => (RootResCon m e c que mut sub) =>
         GQLRootResolver m e c que mut sub -> k

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

instance Interpreter (GQLRequest -> m GQLResponse) m e c where
  interpreter = statelessResolver

instance Interpreter (StateLess m LB.ByteString) m e c where
  interpreter root = byteStringIO (statelessResolver root)

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

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

instance Interpreter (StateLess m Text) m e c 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 e c a = GQLState m e c -> a -> m a

instance Interpreter (WSPub IO e c LB.ByteString) IO e c where
  interpreter root state =
    statefulResolver state (byteStringIO (streamResolver root))

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

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

instance Interpreter (WSPub IO e c Text) IO e c 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 e c a = a -> ResponseStream m e c a

instance Interpreter (GQLRequest -> ResponseStream m e c LB.ByteString) m e c where
  interpreter root request = encode <$> streamResolver root request

instance Interpreter (WSSub m e c LB.ByteString) m e c where
  interpreter root = byteStringIO (streamResolver root)

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

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

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