@*
 * 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.scaladsl.Service)

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

import scala.concurrent.ExecutionContext

import org.apache.pekko
import pekko.actor.ClassicActorSystemProvider

import pekko.grpc.GrpcChannel
import pekko.grpc.GrpcClientCloseException
import pekko.grpc.GrpcClientSettings

import pekko.grpc.scaladsl.PekkoGrpcClient

import pekko.grpc.internal.NettyClientUtils

import pekko.grpc.PekkoGrpcGenerated

@{
  def withSingleResponse(stmt: String) = Set("import pekko.grpc.scaladsl.SingleResponseRequestBuilder", stmt)
  def withStreamResponse(stmt: String) = Set("import pekko.grpc.scaladsl.StreamResponseRequestBuilder", stmt)
  service.methods.flatMap { (method: org.apache.pekko.grpc.gen.scaladsl.Method) =>

    val statements = method.methodType match {
      case org.apache.pekko.grpc.gen.Unary => withSingleResponse("import pekko.grpc.internal.ScalaUnaryRequestBuilder")
      case org.apache.pekko.grpc.gen.ClientStreaming =>  withSingleResponse("import pekko.grpc.internal.ScalaClientStreamingRequestBuilder")
      case org.apache.pekko.grpc.gen.ServerStreaming => withStreamResponse("import pekko.grpc.internal.ScalaServerStreamingRequestBuilder")
      case org.apache.pekko.grpc.gen.BidiStreaming => withStreamResponse("import pekko.grpc.internal.ScalaBidirectionalStreamingRequestBuilder")
    }

    statements
  }.distinct.mkString("\n")
}

// Not sealed so users can extend to write their stubs
@@PekkoGrpcGenerated
trait @{service.name}Client extends @{service.name} with @{service.name}ClientPowerApi with PekkoGrpcClient

@@PekkoGrpcGenerated
object @{service.name}Client {
  def apply(settings: GrpcClientSettings)(implicit sys: ClassicActorSystemProvider): @{service.name}Client =
    new Default@{service.name}Client(GrpcChannel(settings), isChannelOwned = true)
  def apply(channel: GrpcChannel)(implicit sys: ClassicActorSystemProvider): @{service.name}Client =
    new Default@{service.name}Client(channel, isChannelOwned = false)

  private class Default@{service.name}Client(channel: GrpcChannel, isChannelOwned: Boolean)(implicit sys: ClassicActorSystemProvider) extends @{service.name}Client {
    import @{service.name}.MethodDescriptors.@{service.scalaCompatConstants.WildcardImport}

    private implicit val ex: ExecutionContext = sys.classicSystem.dispatcher
    private val settings = channel.settings
    private val options = NettyClientUtils.callOptions(settings)

    @for(method <- service.methods) {
    private def @{method.name}RequestBuilder(channel: pekko.grpc.internal.InternalChannel) =
    @if(method.methodType == org.apache.pekko.grpc.gen.Unary) {
      new ScalaUnaryRequestBuilder(@{method.name}Descriptor, channel, options, settings)
    } else {
      @if(method.methodType == org.apache.pekko.grpc.gen.ServerStreaming) {
      new ScalaServerStreamingRequestBuilder(@{method.name}Descriptor, channel, options, settings)
      } else if(method.methodType == org.apache.pekko.grpc.gen.ClientStreaming) {
      new ScalaClientStreamingRequestBuilder(@{method.name}Descriptor, channel, options, settings)
      } else if (method.methodType == org.apache.pekko.grpc.gen.BidiStreaming) {
      new ScalaBidirectionalStreamingRequestBuilder(@{method.name}Descriptor, channel, options, settings)
      }
    }
    }

    @for(method <- service.methods) {
    /**
     * Lower level "lifted" version of the method, giving access to request metadata etc.
     * prefer @{method.nameSafe}(@method.parameterType) if possible.
     */
    @if(method.methodType == org.apache.pekko.grpc.gen.Unary || method.methodType == org.apache.pekko.grpc.gen.ClientStreaming) {
    override def @{method.nameSafe}(): SingleResponseRequestBuilder[@method.parameterType, @method.outputTypeUnboxed] =
      @{method.name}RequestBuilder(channel.internalChannel)
    } else {
    override def @{method.nameSafe}(): StreamResponseRequestBuilder[@method.parameterType, @method.outputTypeUnboxed] =
      @{method.name}RequestBuilder(channel.internalChannel)
    }

    /**
     * For access to method metadata use the parameterless version of @{method.nameSafe}
     */
    def @{method.nameSafe}(in: @method.parameterType): @method.returnType =
      @{method.nameSafe}().invoke(in)
    }

    override def close(): scala.concurrent.Future[pekko.Done] =
      if (isChannelOwned) channel.close()
      else throw new GrpcClientCloseException()

    override def closed: scala.concurrent.Future[pekko.Done] = channel.closed()
  }
}

@@PekkoGrpcGenerated
trait @{service.name}ClientPowerApi {
  @for(method <- service.methods) {
  /**
   * Lower level "lifted" version of the method, giving access to request metadata etc.
   * prefer @{method.nameSafe}(@method.parameterType) if possible.
   */
  @if(method.methodType == org.apache.pekko.grpc.gen.Unary || method.methodType == org.apache.pekko.grpc.gen.ClientStreaming) {
  def @{method.nameSafe}(): SingleResponseRequestBuilder[@method.parameterType, @method.outputTypeUnboxed] = ???
  } else {
  def @{method.nameSafe}(): StreamResponseRequestBuilder[@method.parameterType, @method.outputTypeUnboxed] = ???
  }
  }

}
