-- Copyright (c) Microsoft. All rights reserved.

-- Licensed under the MIT license. See LICENSE file in the project root for full license information.



{-# LANGUAGE OverloadedStrings, QuasiQuotes, RecordWildCards #-}



module Language.Bond.Codegen.Cs.Grpc_cs (

  grpc_cs)

where



import Data.Monoid

import qualified Data.Text.Lazy as L

import qualified Language.Bond.Codegen.Cs.Util as CS

import Language.Bond.Codegen.TypeMapping

import Language.Bond.Codegen.Util

import Language.Bond.Syntax.Types hiding (methodTypeToMaybe)

import Language.Bond.Util

import Prelude

import Text.Shakespeare.Text



-- | Represents a parameter in an abstract server method.

--

-- This data definition would be in the where clause of grpc_cs if data

-- definitions were permitted in where clauses.

data CsServerMethodParam = CsServerMethodParam { name :: L.Text, csType :: L.Text }



-- | Generates a C# parameter declaration for a server method parameter.

csServerParamDecl :: CsServerMethodParam -> L.Text

csServerParamDecl (CsServerMethodParam {..}) = [lt|#{csType} #{name}|]



-- | Contains splicable text for a client method's first parameter. Create

-- one of these with the function csClientRequestParam. Some kinds of

-- methods don't need a first param, so some of the record's fields may be

-- empty. If non-empty, the correct comma and space are included so this

-- text can just be spliced in.

--

-- This data definition would be in the where clause of grpc_cs if data

-- definitions were permitted in where clauses.

data CsClientRequestParam = CsClientRequestParam

  { bareParam :: L.Text -- bare param declaration: what the caller passes in, if applicable

  , messageParam :: L.Text -- param declaration wrapped in an IMessage

  , toMessage :: L.Text -- code to create an IMessage for the request

  , forwardMessageParam :: L.Text -- code to forward the IMessage param on to the CallInvoker

  }



-- | Codegen template for generating code containing declarations of

-- of services including proxies and services files for gRPC integration.

grpc_cs :: MappingContext -> String -> [Import] -> [Declaration] -> (String, L.Text)

grpc_cs cs _ _ declarations = ("_grpc.cs", [lt|

#{CS.disableCscWarnings}

#{CS.disableReSharperWarnings}



namespace #{csNamespace}

{

    using System.Collections.Generic;



    #{doubleLineSep 1 grpc declarations}

} // #{csNamespace}

|])

  where

    csNamespace = sepBy "." toText $ getNamespace cs

    idl = MappingContext idlTypeMapping [] [] []



    grpc s@Service{..} = [lt|#{CS.typeAttributes cs s}public static class #{declName}#{generics} #{genericsWhere}

    {

        static readonly string ServiceName = "#{getDeclTypeName idl s}";



        #{doubleLineSep 2 methodDeclaration serviceMethods}



        public abstract class #{declName}Base

        {

            #{doubleLineSep 3 serverBaseMethods serviceMethods}

        }



        public class #{declName}Client : global::Grpc.Core.ClientBase<#{declName}Client>

        {

            public #{declName}Client(global::Grpc.Core.Channel channel) : base(channel)

            {

            }



            protected #{declName}Client() : base()

            {

            }



            protected #{declName}Client(global::Grpc.Core.ClientBase.ClientBaseConfiguration configuration) : base(configuration)

            {

            }



            #{doubleLineSep 3 clientMethods serviceMethods}



            protected override #{declName}Client NewInstance(global::Grpc.Core.ClientBase.ClientBaseConfiguration configuration)

            {

                return new #{declName}Client(configuration);

            }

        }



        public static global::Grpc.Core.ServerServiceDefinition BindService(#{declName}Base serviceImpl)

        {

            return global::Grpc.Core.ServerServiceDefinition.CreateBuilder()

                    #{newlineSep 5 serviceMethodBuilder serviceMethods}

                    .Build();

        }

    }

|]

      where

        methodNames = map methodName serviceMethods



        uniqImplName name = uniqueName (name ++ "_impl") methodNames



        csTypeOf Void = "global::Bond.Void"

        csTypeOf (Unary t) = getTypeName cs t

        csTypeOf (Streaming t) = getTypeName cs t



        csRequestTypeName (Function {..}) = csTypeOf methodInput

        csRequestTypeName (Event {..}) = csTypeOf methodInput



        csResponseTypeName (Function {..}) = csTypeOf methodResult

        csResponseTypeName Event{} = csTypeOf Void

      

        messageVoid = [lt|global::Bond.Grpc.IMessage<global::Bond.Void>|]



        messageOf :: Type -> L.Text

        messageOf t = [lt|global::Bond.Grpc.IMessage<#{getTypeName cs t}>|]



        methodTypeEnum :: Method -> L.Text

        methodTypeEnum (Function {..}) = case (methodResult, methodInput) of

            (Streaming _, Streaming _) -> [lt|global::Grpc.Core.MethodType.DuplexStreaming|]

            (Streaming _, _)           -> [lt|global::Grpc.Core.MethodType.ServerStreaming|]

            (_, Streaming _)           -> [lt|global::Grpc.Core.MethodType.ClientStreaming|]

            _                          -> [lt|global::Grpc.Core.MethodType.Unary|]

        methodTypeEnum (Event {..}) = case methodInput of

            Streaming _ -> error ("Unexpected event '" ++ methodName ++ "' with Streaming parameter") -- the parser should have rejected this

            _           -> [lt|global::Grpc.Core.MethodType.Unary|]



        generics = angles $ sepBy ", " paramName declParams



        genericsWhere = sepBy " " addWhere declParams

          where

            addWhere a = [lt|where #{paramName a} : class|]



        methodDeclaration m = [lt|static readonly global::Grpc.Core.Method<global::Bond.Grpc.IMessage<#{csRequestTypeName m}>, global::Bond.Grpc.IMessage<#{csResponseTypeName m}>> Method_#{methodName m} = new global::Grpc.Core.Method<global::Bond.Grpc.IMessage<#{csRequestTypeName m}>, global::Bond.Grpc.IMessage<#{csResponseTypeName m}>>(

            #{methodTypeEnum m},

            ServiceName,

            "#{methodName m}",

            global::Bond.Grpc.Marshaller<#{csRequestTypeName m}>.Instance,

            global::Bond.Grpc.Marshaller<#{csResponseTypeName m}>.Instance);|]



        serverMethodParams :: MethodType -> MethodType -> [CsServerMethodParam]

        serverMethodParams reqType respType = (forRequest reqType) ++ (forResponse respType) ++ context

          where

            forRequest Void = [(CsServerMethodParam  [lt|request|] messageVoid)]

            forRequest (Unary t) = [(CsServerMethodParam [lt|request|] [lt|#{messageOf t}|])]

            forRequest (Streaming t) = [(CsServerMethodParam [lt|requests|] [lt|Grpc.Core.IAsyncStreamReader<#{messageOf t}>|])]

            forResponse Void = []

            forResponse (Unary _) = []

            forResponse (Streaming t) = [(CsServerMethodParam [lt|responses|] [lt|Grpc.Core.IAsyncStreamWriter<#{messageOf t}>|])]

            context = [(CsServerMethodParam [lt|context|] [lt|global::Grpc.Core.ServerCallContext|])]



        -- | For functions (not events), given the result type, compute the

        -- C# abstract method return type.

        csServerFunctionReturn :: MethodType -> L.Text

        csServerFunctionReturn Void = [lt|global::System.Threading.Tasks.Task<#{messageVoid}>|]

        csServerFunctionReturn (Unary t) = [lt|global::System.Threading.Tasks.Task<#{messageOf t}>|]

        csServerFunctionReturn (Streaming _) = [lt|global::System.Threading.Tasks.Task|]



        serverBaseMethods Function{..} = [lt|#{CS.schemaAttributes 3 methodAttributes}public abstract #{csServerFunctionReturn methodResult} #{methodName}(#{csParams});|]

          where params = serverMethodParams methodInput methodResult

                csParams = commaSep csServerParamDecl params



        serverBaseMethods Event{..} = [lt|#{CS.schemaAttributes 3 methodAttributes}public abstract global::System.Threading.Tasks.Task #{methodName}(#{csParams});



            internal global::System.Threading.Tasks.Task<#{messageVoid}> #{uniqImplName methodName}(#{csParams}) {

                return global::Bond.Grpc.Internal.NothingCallHandler.Handle(#{handlerArgs});

            }|]

          where params = serverMethodParams methodInput Void

                csParams = commaSep csServerParamDecl params

                handlerArgs = commaSep id ((L.pack methodName):(map name params))



        -- | For functions (not events), given the input and result types,

        -- compute the C# client method return type.

        csClientFunctionReturn :: MethodType -> MethodType -> L.Text

        csClientFunctionReturn (Streaming input) (Streaming result) = [lt|global::Grpc.Core.AsyncDuplexStreamingCall<#{messageOf input}, #{messageOf result}>|]

        csClientFunctionReturn (Streaming input) (Unary result) = [lt|global::Grpc.Core.AsyncClientStreamingCall<#{messageOf input}, #{messageOf result}>|]

        csClientFunctionReturn _ (Streaming result) = [lt|global::Grpc.Core.AsyncServerStreamingCall<#{messageOf result}>|]

        csClientFunctionReturn _ (Unary result) = [lt|global::Grpc.Core.AsyncUnaryCall<#{messageOf result}>|]

        csClientFunctionReturn _ Void = [lt|global::Grpc.Core.AsyncUnaryCall<#{messageVoid}>|]



        -- | Given the input type, compute the client method parameter.

        csClientRequestParam :: MethodType -> CsClientRequestParam

        csClientRequestParam Void = CsClientRequestParam

          { bareParam = mempty

          , messageParam = [lt|#{messageVoid} request, |]

          , toMessage = [lt|global::Bond.Grpc.Message.Void, |]

          , forwardMessageParam = [lt|, request|] }

        csClientRequestParam (Unary t) = CsClientRequestParam

          { bareParam = [lt|#{getTypeName cs t} request, |]

          , messageParam = [lt|#{messageOf t} request, |]

          , toMessage = [lt|global::Bond.Grpc.Message.From(request), |]

          , forwardMessageParam = [lt|, request|] }

        csClientRequestParam (Streaming _) = CsClientRequestParam

          { bareParam = mempty

          , messageParam = mempty

          , toMessage = mempty

          , forwardMessageParam = mempty }



        -- | Given the input and result types, compute the call invoker needed.

        clientCallInvoker :: MethodType -> MethodType -> L.Text

        clientCallInvoker (Streaming _) (Streaming _) = [lt|AsyncDuplexStreamingCall|]

        clientCallInvoker (Streaming _) (Unary _)     = [lt|AsyncClientStreamingCall|]

        clientCallInvoker (Unary _) (Streaming _)     = [lt|AsyncServerStreamingCall|]

        clientCallInvoker _ _                         = [lt|AsyncUnaryCall|]



        clientMethods Function{..} = [lt|#{CS.schemaAttributes 3 methodAttributes}public virtual #{csClientFunctionReturn methodInput methodResult} #{methodName}Async(#{bareParam requestParam}global::Grpc.Core.Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))

            {

                return #{methodName}Async(#{toMessage requestParam}new global::Grpc.Core.CallOptions(headers, deadline, cancellationToken));

            }



            #{CS.schemaAttributes 3 methodAttributes}public virtual #{csClientFunctionReturn methodInput methodResult} #{methodName}Async(#{messageParam requestParam}global::Grpc.Core.CallOptions options)

            {

                return CallInvoker.#{clientCallInvoker methodInput methodResult}(Method_#{methodName}, null, options#{forwardMessageParam requestParam});

            }|]

          where requestParam = csClientRequestParam methodInput



        clientMethods Event{..} = [lt|#{CS.schemaAttributes 3 methodAttributes}public virtual void #{methodName}Async(#{bareParam requestParam}global::Grpc.Core.Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))

            {

                #{methodName}Async(#{toMessage requestParam}new global::Grpc.Core.CallOptions(headers, deadline, cancellationToken));

            }



            #{CS.schemaAttributes 3 methodAttributes}public virtual void #{methodName}Async(#{messageParam requestParam}global::Grpc.Core.CallOptions options)

            {

                global::Bond.Grpc.Internal.NothingCallInvoker.NothingCall(CallInvoker, Method_#{methodName}, null, options#{forwardMessageParam requestParam});

            }|]

          where requestParam = csClientRequestParam methodInput



        serviceMethodBuilder Function{..} = [lt|.AddMethod(Method_#{methodName}, serviceImpl.#{methodName})|]

        serviceMethodBuilder Event{..} = [lt|.AddMethod(Method_#{methodName}, serviceImpl.#{uniqImplName methodName})|]



    grpc _ = mempty