{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE InstanceSigs               #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE NamedFieldPuns             #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE PolyKinds                  #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}
{-# LANGUAGE UndecidableInstances       #-}
{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE StandaloneDeriving         #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Data.Morpheus.Types.Internal.Resolving.Resolver
  ( Event(..)
  , GQLRootResolver(..)
  , UnSubResolver
  , Resolver
  , MapStrategy(..)
  , LiftOperation
  , unsafeBind
  , toResolver
  , lift
  , subscribe
  , SubEvent
  , GQLChannel(..)
  , ResponseEvent(..)
  , ResponseStream
  , ObjectDeriving(..)
  , Deriving(..)
  , FieldRes
  , WithOperation
  , Context(..)
  , unsafeInternalContext
  , runResolverModel
  , setTypeName
  , ResolverModel(..)
  , liftStateless
  )
where

import           Control.Monad.Fail             (MonadFail(..))
import           Control.Monad.Trans.Class      ( MonadTrans(..))
import           Control.Monad.IO.Class         ( MonadIO(..) )
import           Data.Maybe                     ( maybe )
import           Data.Semigroup                 ( (<>)
                                                , Semigroup(..)
                                                )
import           Control.Monad.Trans.Reader     (ReaderT(..), ask,mapReaderT, withReaderT)
import           Data.Text                      (pack)

-- MORPHEUS
import           Data.Morpheus.Error.Internal   ( internalResolvingError )
import           Data.Morpheus.Error.Selection  ( subfieldsNotSelected )
import           Data.Morpheus.Types.Internal.AST.Selection
                                                ( Selection(..)
                                                , SelectionContent(..)
                                                , SelectionSet
                                                , UnionTag(..)
                                                , UnionSelection
                                                , Operation(..)
                                                )
import           Data.Morpheus.Types.Internal.AST.Base
                                                ( Message
                                                , Name
                                                , OperationType
                                                , QUERY
                                                , MUTATION
                                                , SUBSCRIPTION
                                                , GQLErrors
                                                , GQLError(..)
                                                , VALID
                                                , OperationType(..)
                                                )
import           Data.Morpheus.Types.Internal.AST.Data
                                                ( Schema
                                                , Arguments
                                                )
import           Data.Morpheus.Types.Internal.AST.MergeSet
                                                (toOrderedMap)
import           Data.Morpheus.Types.Internal.Operation
                                                ( selectOr
                                                , empty
                                                , keyOf
                                                , Merge(..)
                                                )
import           Data.Morpheus.Types.Internal.Resolving.Core
                                                ( Eventless
                                                , Result(..)
                                                , Failure(..)
                                                , ResultT(..)
                                                , cleanEvents
                                                , mapEvent
                                                , Event(..)
                                                , Channel(..)
                                                , StreamChannel
                                                , GQLChannel(..)
                                                , PushEvents(..)
                                                , statelessToResultT
                                                )
import           Data.Morpheus.Types.Internal.AST.Value
                                                ( GQLValue(..)
                                                , ValidValue
                                                , ObjectEntry(..)
                                                , Value(..)
                                                , ScalarValue(..)
                                                )
import           Data.Morpheus.Types.IO         ( renderResponse
                                                , GQLResponse
                                                )

type WithOperation (o :: OperationType) = LiftOperation o

type ResponseStream event (m :: * -> *) = ResultT (ResponseEvent event m) m

data ResponseEvent event (m :: * -> *)
  = Publish event
  | Subscribe (SubEvent event m)

type SubEvent event m = Event (Channel event) (event -> m GQLResponse)

data Context
  = Context
    { currentSelection :: Selection VALID
    , schema :: Schema
    , operation :: Operation VALID
    , currentTypeName :: Name
    } deriving (Show)

-- Resolver Internal State
newtype ResolverState event m a
  = ResolverState
    {
      runResolverState :: ReaderT Context (ResultT event m) a
    }
    deriving
      ( Functor
      , Applicative
      , Monad
      )

instance Monad m => MonadFail (ResolverState event m) where
  fail = failure . pack

instance MonadTrans (ResolverState e) where
  lift = ResolverState . lift . lift

instance (Monad m) => Failure Message (ResolverState e m) where
  failure message = ResolverState $ do
    selection <- currentSelection <$> ask
    lift $ failure [resolverFailureMessage selection message]

instance (Monad m) => Failure GQLErrors (ResolverState e m) where
  failure = ResolverState . lift . failure

instance (Monad m) => PushEvents e (ResolverState e m) where
    pushEvents = ResolverState . lift . pushEvents

mapResolverState ::
  ( ReaderT Context (ResultT e m) a
    -> ReaderT Context (ResultT e' m') a'
  ) -> ResolverState e m a
    -> ResolverState e' m' a'
mapResolverState f (ResolverState x) = ResolverState (f x)

getState :: (Monad m) => ResolverState e m (Selection VALID)
getState = ResolverState $ currentSelection <$> ask

mapState :: (Context -> Context ) -> ResolverState e m a -> ResolverState e m a
mapState f = mapResolverState (withReaderT f)

-- clear evets and starts new resolver with diferenct type of events but with same value
-- use properly. only if you know what you are doing
clearStateResolverEvents :: (Functor m) => ResolverState e m a -> ResolverState e' m a
clearStateResolverEvents = mapResolverState (mapReaderT cleanEvents)

resolverFailureMessage :: Selection VALID -> Message -> GQLError
resolverFailureMessage Selection { selectionName, selectionPosition } message = GQLError
  { message   = "Failure on Resolving Field \"" <> selectionName <> "\": " <> message
  , locations = [selectionPosition]
  }

--     
-- GraphQL Field Resolver
--
---------------------------------------------------------------
data Resolver (o::OperationType) event (m :: * -> * )  value where
    ResolverQ :: { runResolverQ :: ResolverState () m value } -> Resolver QUERY event m value
    ResolverM :: { runResolverM :: ResolverState event m value } -> Resolver MUTATION event m  value
    ResolverS :: { runResolverS :: ResolverState (Channel event) m (ReaderT event (Resolver QUERY event m) value) } -> Resolver SUBSCRIPTION event m  value

instance Show (Resolver o e m value) where
  show ResolverQ {} = "Resolver QUERY e m a"
  show ResolverM {} = "Resolver MUTATION e m a"
  show ResolverS {} = "Resolver SUBSCRIPTION e m a"

deriving instance (Functor m) => Functor (Resolver o e m)

-- Applicative
instance (LiftOperation o ,Monad m) => Applicative (Resolver o e m) where
  pure = packResolver . pure
  ResolverQ r1 <*> ResolverQ r2 = ResolverQ $ r1 <*> r2
  ResolverM r1 <*> ResolverM r2 = ResolverM $ r1 <*> r2
  ResolverS r1 <*> ResolverS r2 = ResolverS $ (<*>) <$> r1 <*> r2

-- Monad 
instance (Monad m, LiftOperation o) => Monad (Resolver o e m) where
  return = pure
  (>>=) = unsafeBind

-- MonadIO
instance (MonadIO m) => MonadIO (Resolver QUERY e m) where
    liftIO = lift . liftIO

instance (MonadIO m) => MonadIO (Resolver MUTATION e m) where
    liftIO = lift . liftIO

-- Monad Transformers    
instance MonadTrans (Resolver QUERY e) where
  lift = packResolver . lift

instance MonadTrans (Resolver MUTATION e) where
  lift = packResolver . lift

-- Failure
instance (LiftOperation o, Monad m) => Failure Message (Resolver o e m) where
  failure = packResolver .failure

instance (LiftOperation o, Monad m) => Failure GQLErrors (Resolver o e m) where
  failure = packResolver . failure

-- PushEvents
instance (Monad m) => PushEvents e (Resolver MUTATION e m)  where
  pushEvents = packResolver . pushEvents

liftStateless
  :: ( LiftOperation o
     , Monad m
     )
  => Eventless a
  -> Resolver o e m a
liftStateless
  = packResolver
  . ResolverState
  . ReaderT
  . const
  . statelessToResultT


class LiftOperation (o::OperationType) where
  packResolver :: Monad m => ResolverState e m a -> Resolver o e m a
  withResolver :: Monad m => ResolverState e m a -> (a -> Resolver o e m b) -> Resolver o e m b

-- packResolver
instance LiftOperation QUERY where
  packResolver = ResolverQ . clearStateResolverEvents
  withResolver ctxRes toRes = ResolverQ $ do
     v <- clearStateResolverEvents ctxRes
     runResolverQ $ toRes v

instance LiftOperation MUTATION where
  packResolver = ResolverM
  withResolver ctxRes toRes = ResolverM $ ctxRes >>= runResolverM . toRes

instance LiftOperation SUBSCRIPTION where
  packResolver = ResolverS . pure . lift . packResolver
  withResolver ctxRes toRes = ResolverS $ do
    value <- clearStateResolverEvents ctxRes
    runResolverS $ toRes value


mapResolverContext :: Monad m => (Context -> Context) -> Resolver o e m a -> Resolver o e m a
mapResolverContext f (ResolverQ res)  = ResolverQ (mapState f res)
mapResolverContext f (ResolverM res)  = ResolverM (mapState f res)
mapResolverContext f (ResolverS resM)  = ResolverS $ do
    res <- resM
    pure $ ReaderT $ \e -> ResolverQ $ mapState f (runResolverQ (runReaderT res e))

setSelection :: Monad m => Selection VALID -> Resolver o e m a -> Resolver o e m a
setSelection currentSelection
  = mapResolverContext (\ctx -> ctx { currentSelection })

setTypeName :: Monad m => Name -> Resolver o e m a -> Resolver o e m a
setTypeName  currentTypeName
  = mapResolverContext (\ctx -> ctx { currentTypeName } )

-- unsafe variant of >>= , not for public api. user can be confused: 
--  ignores `channels` on second Subsciption, only returns events from first Subscription monad.
--    reason: second monad is waiting for `event` until he does not have some event can't tell which 
--            channel does it want to listen
unsafeBind
  :: forall o e m a b
   . Monad m
  =>  Resolver o e m a
  -> (a -> Resolver o e m b)
  -> Resolver o e m b
unsafeBind (ResolverQ x) m2 = ResolverQ (x >>= runResolverQ . m2)
unsafeBind (ResolverM x) m2 = ResolverM (x >>= runResolverM . m2)
unsafeBind (ResolverS res) m2 = ResolverS $ do
    (readResA :: ReaderT e (Resolver QUERY e m) a ) <- res
    pure $ ReaderT $ \e -> ResolverQ $ do
         let (resA :: Resolver QUERY e m a) = runReaderT readResA e
         (valA :: a) <- runResolverQ resA
         (readResB :: ReaderT e (Resolver QUERY e m) b) <- clearStateResolverEvents $ runResolverS (m2 valA)
         runResolverQ $ runReaderT readResB e

subscribe
  :: forall e m a
    . ( PushEvents (Channel e) (ResolverState (Channel e) m)
      , Monad m
      )
    => [StreamChannel e]
    -> Resolver QUERY e m (e -> Resolver QUERY e m a)
    -> Resolver SUBSCRIPTION e m a
subscribe ch res = ResolverS $ do
  pushEvents (map Channel ch :: [Channel e])
  (eventRes :: e -> Resolver QUERY e m a) <- clearStateResolverEvents (runResolverQ res)
  pure $ ReaderT eventRes

unsafeInternalContext :: (Monad m, LiftOperation o) => Resolver o e m Context
unsafeInternalContext = packResolver $ ResolverState ask

-- Converts Subscription Resolver Type to Query Resolver
type family UnSubResolver (a :: * -> * ) :: (* -> *)
type instance UnSubResolver (Resolver SUBSCRIPTION e m) = Resolver QUERY e m

-- map Resolving strategies 
class MapStrategy
  (from :: OperationType)
  (to :: OperationType) where
   mapStrategy
    :: Monad m
    => Resolver from e m (Deriving from e m)
    -> Resolver to e m (Deriving to e m)

instance MapStrategy o o where
  mapStrategy = id

data Deriving (o :: OperationType) e (m ::  * -> * )
  = DerivingNull
  | DerivingScalar    ScalarValue
  | DerivingEnum      Name Name
  | DerivingList      [Deriving o e m]
  | DerivingObject    (ObjectDeriving o e m)
  | DerivingUnion     Name (Resolver o e m (Deriving o e m))
  deriving (Show)


data ObjectDeriving o e m
  = ObjectDeriving {
      __typename :: Name,
      objectFields :: [
        ( Name
        , Resolver o e m (Deriving o e m)
        )
      ]
    } deriving (Show)

instance MapStrategy QUERY SUBSCRIPTION where
  mapStrategy  = ResolverS . pure . lift . fmap mapDeriving

mapDeriving
  ::  ( MapStrategy o o'
      , Monad m
      )
  => Deriving o e m
  -> Deriving o' e m
mapDeriving DerivingNull = DerivingNull
mapDeriving (DerivingScalar x) = DerivingScalar x
mapDeriving (DerivingEnum typeName enum) = DerivingEnum typeName enum
mapDeriving (DerivingList x)  = DerivingList $  map mapDeriving x
mapDeriving (DerivingObject x)  = DerivingObject (mapObjectDeriving x)
mapDeriving (DerivingUnion name x) = DerivingUnion name (mapStrategy x)

mapObjectDeriving
  ::  ( MapStrategy o o'
      , Monad m
      )
  => ObjectDeriving o e m
  -> ObjectDeriving o' e m
mapObjectDeriving (ObjectDeriving tyname x)
      = ObjectDeriving tyname
        $ map (mapEntry mapStrategy) x

mapEntry :: (a -> b) -> (Name, a) -> (Name, b)
mapEntry f (name,value) = (name, f value)

--
-- Selection Processing
toResolver
  :: forall o e m a b. (LiftOperation o, Monad m)
  => (Arguments VALID -> Eventless a)
  -> ( a -> Resolver o e m b)
  -> Resolver o e m b
toResolver toArgs  = withResolver args
 where
  args :: ResolverState e m a
  args = do
    Selection { selectionArguments } <- getState
    let resT = ResultT $ pure $ toArgs selectionArguments
    ResolverState $ lift $ cleanEvents resT

-- DataResolver
type FieldRes o e m
  = (Name, Resolver o e m (Deriving o e m))

instance Merge (Deriving o e m) where
  merge p (DerivingObject x) (DerivingObject y)
    = DerivingObject <$> merge p x y
  merge _ _ _
    = failure $ internalResolvingError "can't merge: incompatible resolvers"

instance Merge (ObjectDeriving o e m) where
  merge _ (ObjectDeriving tyname x) (ObjectDeriving _ y)
    = pure $ ObjectDeriving tyname (x <> y)


pickSelection :: Name -> UnionSelection -> SelectionSet VALID
pickSelection = selectOr empty unionTagSelection

withObject
  :: (LiftOperation o, Monad m)
  => (SelectionSet VALID -> Resolver o e m value)
  -> Selection VALID
  -> Resolver o e m value
withObject f Selection { selectionName, selectionContent , selectionPosition } = checkContent selectionContent
 where
  checkContent (SelectionSet selection) = f selection
  checkContent _ = failure (subfieldsNotSelected selectionName "" selectionPosition)

lookupRes
  :: (LiftOperation o, Monad m)
  => Selection VALID
  -> ObjectDeriving o e m
  -> Resolver o e m ValidValue
lookupRes
  Selection { selectionName }
  | selectionName == "__typename"
      =  pure . Scalar . String . __typename
  | otherwise
      = maybe
        (pure gqlNull)
        (`unsafeBind` runDataResolver)
        . lookup selectionName
        . objectFields

resolveObject
  :: forall o e m. (LiftOperation o , Monad m)
  => SelectionSet VALID
  -> Deriving o e m
  -> Resolver o e m ValidValue
resolveObject selectionSet (DerivingObject drv@ObjectDeriving { __typename }) =
  Object . toOrderedMap <$> traverse resolver selectionSet
 where
  resolver :: Selection VALID -> Resolver o e m (ObjectEntry VALID)
  resolver sel
    = setSelection sel
      $ setTypeName __typename
      $ ObjectEntry (keyOf sel) <$> lookupRes sel drv
resolveObject _ _ =
  failure $ internalResolvingError "expected object as resolver"

toEventResolver :: Monad m => ReaderT event (Resolver QUERY event m) ValidValue -> Context -> event -> m GQLResponse
toEventResolver (ReaderT subRes) sel event = do
  value <- runResultT $ runReaderT (runResolverState $ runResolverQ (subRes event)) sel
  pure $ renderResponse value


runDataResolver :: (Monad m, LiftOperation o) => Deriving o e m -> Resolver o e m ValidValue
runDataResolver = withResolver getState . __encode
   where
    __encode obj sel@Selection { selectionContent }  = encodeNode obj selectionContent
      where
      -- LIST
      encodeNode (DerivingList x) _ = List <$> traverse runDataResolver x
      -- Object -----------------
      encodeNode objDrv@DerivingObject{} _ = withObject (`resolveObject` objDrv) sel
      -- ENUM
      encodeNode (DerivingEnum _ enum) SelectionField = pure $ gqlString enum
      encodeNode (DerivingEnum typename enum) unionSel@UnionSelection{}
        = encodeNode (unionDrv (typename <> "EnumObject")) unionSel
          where
            unionDrv name
              = DerivingUnion name
                $ pure
                $ DerivingObject
                $ ObjectDeriving name [("enum", pure $ DerivingScalar $ String enum)]
      encodeNode DerivingEnum {}  _ =
          failure ( "wrong selection on enum value" :: Message)
      -- UNION
      encodeNode (DerivingUnion typename unionRef) (UnionSelection selections)
        = unionRef >>= resolveObject currentSelection
          where currentSelection = pickSelection typename selections
      encodeNode (DerivingUnion name _) _
        = failure ("union Resolver \""<> name <> "\" should only recieve UnionSelection" :: Message)
      -- SCALARS
      encodeNode DerivingNull _ = pure Null
      encodeNode (DerivingScalar x) SelectionField = pure $ Scalar x
      encodeNode DerivingScalar {} _
        = failure ("scalar Resolver should only recieve SelectionField" :: Message)

runResolver
  :: Monad m
  => Resolver o event m ValidValue
  -> Context
  -> ResponseStream event m ValidValue
runResolver (ResolverQ resT) sel = cleanEvents $ (runReaderT $ runResolverState resT) sel
runResolver (ResolverM resT) sel = mapEvent Publish $ (runReaderT $ runResolverState resT) sel
runResolver (ResolverS resT) sel = ResultT $ do
    (readResValue :: Result (Channel event1) (ReaderT event (Resolver QUERY event m) ValidValue))  <- runResultT $ (runReaderT $ runResolverState resT) sel
    pure $ case readResValue of
      Failure x -> Failure x
      Success { warnings ,result , events = channels } -> do
        let eventRes = toEventResolver result sel
        Success {
          events = [Subscribe $ Event channels eventRes],
          warnings,
          result = gqlNull
        }

runRootDataResolver
  :: (Monad m , LiftOperation o)
  => Eventless (Deriving o e m)
  -> Context
  -> ResponseStream e m (Value VALID)
runRootDataResolver
    res
    ctx@Context { operation = Operation { operationSelection } }
  = do
    root <- statelessToResultT res
    runResolver (resolveObject operationSelection root) ctx

-------------------------------------------------------------------
-- | GraphQL Root resolver, also the interpreter generates a GQL schema from it.
--  'queryResolver' is required, 'mutationResolver' and 'subscriptionResolver' are optional,
--  if your schema does not supports __mutation__ or __subscription__ , you can use __()__ for it.
data GQLRootResolver (m :: * -> *) event (query :: (* -> *) -> * ) (mut :: (* -> *) -> * )  (sub :: (* -> *) -> * )  = GQLRootResolver
  { queryResolver        :: query (Resolver QUERY event m)
  , mutationResolver     :: mut (Resolver MUTATION event m)
  , subscriptionResolver :: sub (Resolver SUBSCRIPTION  event m)
  }

data ResolverModel e m
    = ResolverModel
      { query :: Eventless (Deriving QUERY e m)
      , mutation :: Eventless (Deriving MUTATION e m)
      , subscription :: Eventless (Deriving SUBSCRIPTION e m)
      }

runResolverModel :: Monad m => ResolverModel e m -> Context -> ResponseStream e m (Value VALID)
runResolverModel
    ResolverModel
      { query
      , mutation
      , subscription
      }
    ctx@Context { operation = Operation { operationType} }
  = selectByOperation operationType
  where
    selectByOperation Query
      = runRootDataResolver query ctx
    selectByOperation Mutation
      = runRootDataResolver mutation ctx
    selectByOperation Subscription
      = runRootDataResolver subscription ctx