@*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 *@

@*
 * Copyright (C) 2018-2021 Lightbend Inc. <https://www.lightbend.com>
 *@

@(service: org.apache.pekko.grpc.gen.javadsl.Service)

@org.apache.pekko.grpc.gen.Constants.DoNotEditComment
package @service.packageName;

import org.apache.pekko.actor.ClassicActorSystemProvider;
import org.apache.pekko.stream.Materializer;
import org.apache.pekko.stream.SystemMaterializer;

import org.apache.pekko.grpc.internal.*;
import org.apache.pekko.grpc.GrpcChannel;
import org.apache.pekko.grpc.GrpcClientCloseException;
import org.apache.pekko.grpc.GrpcClientSettings;
import org.apache.pekko.grpc.javadsl.PekkoGrpcClient;

import io.grpc.MethodDescriptor;

import static @{service.packageName}.@{service.name}.Serializers.*;

import scala.concurrent.ExecutionContext;

import org.apache.pekko.grpc.PekkoGrpcGenerated;

@GenMethodImports(service)

@@PekkoGrpcGenerated
public abstract class @{service.name}Client extends @{service.name}ClientPowerApi implements @{service.name}, PekkoGrpcClient {
  public static final @{service.name}Client create(GrpcClientSettings settings, ClassicActorSystemProvider sys) {
    return new Default@{service.name}Client(org.apache.pekko.grpc.GrpcChannel$.MODULE$.apply(settings, sys), true, sys);
  }

  public static final @{service.name}Client create(GrpcChannel channel, ClassicActorSystemProvider sys) {
    return new Default@{service.name}Client(channel, false, sys);
  }

  @@PekkoGrpcGenerated
  protected final static class Default@{service.name}Client extends @{service.name}Client {

      private final GrpcChannel channel;
      private final boolean isChannelOwned;
      private final GrpcClientSettings settings;
      private final io.grpc.CallOptions options;
      private final Materializer mat;
      private final ExecutionContext ec;

      private Default@{service.name}Client(GrpcChannel channel, boolean isChannelOwned, ClassicActorSystemProvider sys) {
        this.channel = channel;
        this.isChannelOwned = isChannelOwned;
        this.settings = channel.settings();
        this.mat = SystemMaterializer.get(sys).materializer();
        this.ec = sys.classicSystem().dispatcher();
        this.options = NettyClientUtils.callOptions(settings);

        sys.classicSystem().getWhenTerminated().whenComplete((v, e) -> close());
      }

  @for(method <- service.methods) {
    @if(method.methodType == org.apache.pekko.grpc.gen.Unary) {
      private final SingleResponseRequestBuilder<@method.inputTypeUnboxed, @method.outputTypeUnboxed> @{method.name}RequestBuilder(org.apache.pekko.grpc.internal.InternalChannel channel){
        return new JavaUnaryRequestBuilder<>(@{method.name}Descriptor, channel, options, settings, ec);
      }
    } else {
      @if(method.methodType == org.apache.pekko.grpc.gen.ClientStreaming){
        private final SingleResponseRequestBuilder<org.apache.pekko.stream.javadsl.Source<@method.inputTypeUnboxed, org.apache.pekko.NotUsed>, @method.outputTypeUnboxed> @{method.name}RequestBuilder(org.apache.pekko.grpc.internal.InternalChannel channel){
          return new JavaClientStreamingRequestBuilder<>(
                               @{method.name}Descriptor, channel, options, settings, mat, ec);
        }
      } else if(method.methodType == org.apache.pekko.grpc.gen.ServerStreaming){
        private final StreamResponseRequestBuilder<@method.inputTypeUnboxed, @method.outputTypeUnboxed> @{method.name}RequestBuilder(org.apache.pekko.grpc.internal.InternalChannel channel){
          return new JavaServerStreamingRequestBuilder<>(
                               @{method.name}Descriptor, channel, options, settings, ec);
        }
      } else if(method.methodType == org.apache.pekko.grpc.gen.BidiStreaming){
        private final StreamResponseRequestBuilder<org.apache.pekko.stream.javadsl.Source<@method.inputTypeUnboxed, org.apache.pekko.NotUsed>, @method.outputTypeUnboxed> @{method.name}RequestBuilder(org.apache.pekko.grpc.internal.InternalChannel channel){
          return new JavaBidirectionalStreamingRequestBuilder<>(
                               @{method.name}Descriptor, channel, options, settings, ec);
        }
      }
    }
  }

      @for(method <- service.methods) {

        /**
         * For access to method metadata use the parameterless version of @{method.name}
         */
        public @{method.getReturnType} @{method.name}(@{method.getParameterType} request) {
          return @{method.name}().invoke(request);
        }

        /**
         * Lower level "lifted" version of the method, giving access to request metadata etc.
         * prefer @{method.name}(@method.inputTypeUnboxed) if possible.
         */
        @if(method.methodType == org.apache.pekko.grpc.gen.Unary) {
          public SingleResponseRequestBuilder<@method.inputTypeUnboxed, @method.outputTypeUnboxed> @{method.name}()
        }else if(method.methodType == org.apache.pekko.grpc.gen.ClientStreaming){
          public SingleResponseRequestBuilder<org.apache.pekko.stream.javadsl.Source<@method.inputTypeUnboxed, org.apache.pekko.NotUsed>, @method.outputTypeUnboxed> @{method.name}()
        }else if(method.methodType == org.apache.pekko.grpc.gen.ServerStreaming){
          public StreamResponseRequestBuilder<@method.inputTypeUnboxed, @method.outputTypeUnboxed> @{method.name}()
        }else if(method.methodType == org.apache.pekko.grpc.gen.BidiStreaming){
          public StreamResponseRequestBuilder<org.apache.pekko.stream.javadsl.Source<@method.inputTypeUnboxed, org.apache.pekko.NotUsed>, @method.outputTypeUnboxed> @{method.name}()
        }
        {
          return @{method.name}RequestBuilder(channel.internalChannel());
        }
      }

      @for(method <- service.methods) {
        private static MethodDescriptor<@method.inputTypeUnboxed, @method.outputTypeUnboxed> @{method.name}Descriptor =
          MethodDescriptor.<@method.inputTypeUnboxed, @method.outputTypeUnboxed>newBuilder()
            .setType(@mapMethodType(method.methodType))
            .setFullMethodName(MethodDescriptor.generateFullMethodName("@service.grpcName", "@method.grpcName"))
            .setRequestMarshaller(new ProtoMarshaller<@method.inputTypeUnboxed>(@method.deserializer.name))
            .setResponseMarshaller(new ProtoMarshaller<@method.outputTypeUnboxed>(@method.serializer.name))
            .setSampledToLocalTracing(true)
            .build();
        }

      /**
       * Initiates a shutdown in which preexisting and new calls are cancelled.
       */
      public java.util.concurrent.CompletionStage<org.apache.pekko.Done> close() {
        if (isChannelOwned) {
          return channel.closeCS();
        } else {
          throw new GrpcClientCloseException();
        }
      }

     /**
      * Returns a CompletionState that completes successfully when shutdown via close()
      * or exceptionally if a connection can not be established after maxConnectionAttempts.
      */
      public java.util.concurrent.CompletionStage<org.apache.pekko.Done> closed() {
        return channel.closedCS();
      }
  }

}


@mapMethodType(methodType: org.apache.pekko.grpc.gen.MethodType) = {
  @if(methodType == org.apache.pekko.grpc.gen.Unary) { MethodDescriptor.MethodType.UNARY }
  @if(methodType == org.apache.pekko.grpc.gen.ClientStreaming) {MethodDescriptor.MethodType.CLIENT_STREAMING }
  @if(methodType == org.apache.pekko.grpc.gen.ServerStreaming) { MethodDescriptor.MethodType.SERVER_STREAMING }
  @if(methodType == org.apache.pekko.grpc.gen.BidiStreaming) {MethodDescriptor.MethodType.BIDI_STREAMING }
}
