{-# language ConstraintKinds           #-}
{-# language DataKinds                 #-}
{-# language ExistentialQuantification #-}
{-# language GADTs                     #-}
{-# language PolyKinds                 #-}
{-# language TypeFamilies              #-}
{-# language TypeOperators             #-}
{-# language UndecidableInstances      #-}
{-|
Description : Protocol-independent declaration of services

This module defines a type-level language to describe
RPC-like microservices independently of the transport
and protocol.
-}
module Mu.Rpc (
  Package', Package(..)
, Service', Service(..), Object
, ServiceAnnotation, Method', Method(..), ObjectField
, LookupService, LookupMethod
, TypeRef(..), Argument', Argument(..), Return(..)
) where

import           Data.Kind
import           GHC.TypeLits
import qualified Language.Haskell.TH as TH

import           Mu.Schema
import           Mu.Schema.Registry

-- | Packages whose names are given by type-level strings.
type Package' = Package Symbol Symbol Symbol
-- | Services whose names are given by type-level strings.
type Service' = Service Symbol Symbol Symbol
-- | Methods whose names are given by type-level strings.
type Method' = Method Symbol Symbol Symbol
-- | Arguments whose names are given by type-level strings.
type Argument' = Argument Symbol Symbol
-- | Annotations for services. At this moment, such
--   annotations can be of any type.
type ServiceAnnotation = Type

-- | A package is a set of services.
data Package serviceName methodName argName
  = Package (Maybe serviceName)
            [Service serviceName methodName argName]

-- | A service is a set of methods.
data Service serviceName methodName argName
  = Service serviceName
            [ServiceAnnotation]
            [Method serviceName methodName argName]

-- | A method is defined by its name, arguments, and return type.
data Method serviceName methodName argName
  = Method methodName [ServiceAnnotation]
           [Argument serviceName argName]
           (Return serviceName)

-- Synonyms for GraphQL
-- | An object is a set of fields, in GraphQL lingo.
type Object = 'Service
-- | A field in an object takes some input objects,
--   and returns a value or some other object,
--   in GraphQL lingo.
type ObjectField = 'Method

-- | Look up a service in a package definition using its name.
type family LookupService (ss :: [Service snm mnm anm]) (s :: snm) :: Service snm mnm anm where
  LookupService '[] s = TypeError ('Text "could not find method " ':<>: 'ShowType s)
  LookupService ('Service s anns ms ': ss) s = 'Service s anns ms
  LookupService (other              ': ss) s = LookupService ss s

-- | Look up a method in a service definition using its name.
type family LookupMethod (s :: [Method snm mnm anm]) (m :: mnm) :: Method snm mnm anm where
  LookupMethod '[] m = TypeError ('Text "could not find method " ':<>: 'ShowType m)
  LookupMethod ('Method m anns args r ': ms) m = 'Method m anns args r
  LookupMethod (other                 ': ms) m = LookupMethod ms m

-- | Defines a reference to a type, either primitive or coming from the schema.
--   'TypeRef's are used to define arguments and result types.
data TypeRef serviceName where
  -- | A primitive type.
  PrimitiveRef :: Type -> TypeRef serviceName
  -- | Chain with another service.
  ObjectRef    :: serviceName -> TypeRef serviceName
  -- | Point to schema.
  SchemaRef    :: Schema typeName fieldName -> typeName -> TypeRef serviceName
  -- | Registry subject, type to convert to, and preferred serialization version
  RegistryRef  :: Registry -> Type -> Nat -> TypeRef serviceName
  -- | To be used only during TH generation!
  THRef        :: TH.Type -> TypeRef serviceName
  -- Combinators found in the gRPC and GraphQL languages.
  -- | Represents a list of values.
  ListRef      :: TypeRef serviceName -> TypeRef serviceName
  -- | Represents a possibly-missing value.
  OptionalRef  :: TypeRef serviceName -> TypeRef serviceName

-- | Defines the way in which arguments are handled.
data Argument serviceName argName where
  -- | Use a single value.
  ArgSingle :: Maybe argName -> [ServiceAnnotation]
            -> TypeRef serviceName -> Argument serviceName argName
  -- | Consume a stream of values.
  ArgStream :: Maybe argName -> [ServiceAnnotation]
            -> TypeRef serviceName -> Argument serviceName argName

-- | Defines the different possibilities for returning
--   information from a method.
data Return serviceName where
  -- | Fire and forget.
  RetNothing :: Return serviceName
  -- | Return a single value.
  RetSingle  :: TypeRef serviceName -> Return serviceName
  -- | Return a stream of values.
  RetStream  :: TypeRef serviceName -> Return serviceName
  -- | Return a value or an error.
  RetThrows  :: TypeRef serviceName -> TypeRef serviceName -> Return serviceName