@*
 * 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, powerApis: Boolean)

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

import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import org.apache.pekko.japi.function.Function;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.ClassicActorSystemProvider;
import org.apache.pekko.annotation.ApiMayChange;
import org.apache.pekko.stream.Materializer;
import org.apache.pekko.stream.SystemMaterializer;

import org.apache.pekko.grpc.Trailers;
import org.apache.pekko.grpc.javadsl.GrpcMarshalling;
import org.apache.pekko.grpc.javadsl.GrpcExceptionHandler;
import org.apache.pekko.grpc.internal.TelemetryExtension;
import org.apache.pekko.grpc.internal.TelemetrySpi;

import org.apache.pekko.grpc.PekkoGrpcGenerated;

@{if (powerApis) "import org.apache.pekko.grpc.javadsl.Metadata;\nimport org.apache.pekko.grpc.javadsl.MetadataBuilder;" else ""}

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

@defining(if (powerApis) service.name + "PowerApi" else service.name) { serviceName =>
/*
 * Generated by Pekko gRPC. DO NOT EDIT.
 *
 * The API of this class may still change in future Apache Pekko gRPC versions, see for instance
 * https://github.com/akka/akka-grpc/issues/994
 */
@@ApiMayChange

@@PekkoGrpcGenerated
public class @{serviceName}HandlerFactory {

    private static final CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse> notFound = CompletableFuture.completedFuture(
      org.apache.pekko.http.javadsl.model.HttpResponse.create().withStatus(org.apache.pekko.http.javadsl.model.StatusCodes.NOT_FOUND));

    private static final CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse> unsupportedMediaType = CompletableFuture.completedFuture(
      org.apache.pekko.http.javadsl.model.HttpResponse.create().withStatus(org.apache.pekko.http.javadsl.model.StatusCodes.UNSUPPORTED_MEDIA_TYPE));

    /**
     * Creates a `HttpRequest` to `HttpResponse` handler that can be used in for example `Http().bindAndHandleAsync`
     * for the generated partial function handler and ends with `StatusCodes.NotFound` if the request is not matching.
     *
     * Use {@@link org.apache.pekko.grpc.javadsl.ServiceHandler#concatOrNotFound} with {@@link @{service.name}HandlerFactory#partial} when combining
     * several services.
     */
    public static Function<org.apache.pekko.http.javadsl.model.HttpRequest, CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse>> create(@serviceName implementation, ClassicActorSystemProvider system) {
      return create(implementation, @{service.name}.name, system);
    }

    /**
     * Creates a `HttpRequest` to `HttpResponse` handler that can be used in for example `Http().bindAndHandleAsync`
     * for the generated partial function handler and ends with `StatusCodes.NotFound` if the request is not matching.
     *
     * Use {@@link org.apache.pekko.grpc.javadsl.ServiceHandler#concatOrNotFound} with {@@link @{service.name}HandlerFactory#partial} when combining
     * several services.
     */
    public static Function<org.apache.pekko.http.javadsl.model.HttpRequest, CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse>> create(@serviceName implementation, org.apache.pekko.japi.function.Function<ActorSystem, org.apache.pekko.japi.function.Function<Throwable, Trailers>> eHandler, ClassicActorSystemProvider system) {
      return create(implementation, @{service.name}.name, eHandler, system);
    }

    /**
     * Creates a `HttpRequest` to `HttpResponse` handler that can be used in for example `Http().bindAndHandleAsync`
     * for the generated partial function handler and ends with `StatusCodes.NotFound` if the request is not matching.
     *
     * Use {@@link org.apache.pekko.grpc.javadsl.ServiceHandler#concatOrNotFound} with {@@link @{service.name}HandlerFactory#partial} when combining
     * several services.
     *
     * Registering a gRPC service under a custom prefix is not widely supported and strongly discouraged by the specification.
     */
    public static Function<org.apache.pekko.http.javadsl.model.HttpRequest, CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse>> create(@serviceName implementation, String prefix, ClassicActorSystemProvider system) {
      return partial(implementation, prefix, SystemMaterializer.get(system).materializer(), GrpcExceptionHandler.defaultMapper(), system);
    }

    /**
     * Creates a `HttpRequest` to `HttpResponse` handler that can be used in for example `Http().bindAndHandleAsync`
     * for the generated partial function handler and ends with `StatusCodes.NotFound` if the request is not matching.
     *
     * Use {@@link org.apache.pekko.grpc.javadsl.ServiceHandler#concatOrNotFound} with {@@link @{service.name}HandlerFactory#partial} when combining
     * several services.
     *
     * Registering a gRPC service under a custom prefix is not widely supported and strongly discouraged by the specification.
     */
    public static Function<org.apache.pekko.http.javadsl.model.HttpRequest, CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse>> create(@serviceName implementation, String prefix, org.apache.pekko.japi.function.Function<ActorSystem, org.apache.pekko.japi.function.Function<Throwable, Trailers>> eHandler, ClassicActorSystemProvider system) {
      return partial(implementation, prefix, SystemMaterializer.get(system).materializer(), eHandler, system);
    }

@if(serviceName != "ServerReflection") {

    /**
     * Creates a `HttpRequest` to `HttpResponse` handler that can be used in for example `Http().bindAndHandleAsync`
     * for the generated partial function handler. The generated handler falls back to a reflection handlers for
     * `@{service.name}` and ends with `StatusCodes.NotFound` if the request is not matching.
     *
     * Use {@@link org.apache.pekko.grpc.javadsl.ServiceHandler#concatOrNotFound} with {@@link @{service.name}HandlerFactory#partial} when combining
     * several services.
     */
    @@SuppressWarnings("unchecked")
    public static Function<org.apache.pekko.http.javadsl.model.HttpRequest, CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse>> createWithServerReflection(@serviceName implementation, ClassicActorSystemProvider system) {
      return org.apache.pekko.grpc.javadsl.ServiceHandler.concatOrNotFound(
          @{serviceName}HandlerFactory.create(implementation, system),
          org.apache.pekko.grpc.javadsl.ServerReflection.create(
            java.util.Collections.singletonList(@{service.name}.description),
            system));
    }

}

    /**
     * Creates a `HttpRequest` to `HttpResponse` handler that can be used in for example
     * `Http.get(system).bindAndHandleAsync`. It ends with `StatusCodes.NotFound` if the request is not matching.
     *
     * Use {@@link org.apache.pekko.grpc.javadsl.ServiceHandler#concatOrNotFound} when combining several services.
     */
    public static Function<org.apache.pekko.http.javadsl.model.HttpRequest, CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse>> partial(@serviceName implementation, String prefix, ClassicActorSystemProvider system) {
      return partial(implementation, prefix, SystemMaterializer.get(system).materializer(), GrpcExceptionHandler.defaultMapper(), system);
    }

    /**
     * Creates a `HttpRequest` to `HttpResponse` handler that can be used in for example
     * `Http.get(system).bindAndHandleAsync`. It ends with `StatusCodes.NotFound` if the request is not matching.
     *
     * Use {@@link org.apache.pekko.grpc.javadsl.ServiceHandler#concatOrNotFound} when combining several services.
     */
    public static Function<org.apache.pekko.http.javadsl.model.HttpRequest, CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse>> partial(@serviceName implementation, String prefix, Materializer mat, org.apache.pekko.japi.function.Function<ActorSystem, org.apache.pekko.japi.function.Function<Throwable, Trailers>> eHandler, ClassicActorSystemProvider system) {
      TelemetrySpi spi = TelemetryExtension.get(system).spi();
      return (req -> {
        Iterator<String> segments = req.getUri().pathSegments().iterator();
        if (segments.hasNext() && segments.next().equals(prefix) && segments.hasNext()) {
          String method = segments.next();
          if (segments.hasNext()) return notFound; // we don't allow any random `/prefix/Method/anything/here
          else {
            return handle(spi.onRequest(prefix, method, req), method, implementation, mat, eHandler, system);
          }
        } else {
          return notFound;
        }
      });
    }

    public String getServiceName() {
      return @{service.name}.name;
    }

    private static CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse> handle(org.apache.pekko.http.javadsl.model.HttpRequest request, String method, @serviceName implementation, Materializer mat, org.apache.pekko.japi.function.Function<ActorSystem, org.apache.pekko.japi.function.Function<Throwable, Trailers>> eHandler, ClassicActorSystemProvider system) {
      return GrpcMarshalling.negotiated(request, (reader, writer) -> {
        CompletionStage<org.apache.pekko.http.javadsl.model.HttpResponse> response;
        @{if(powerApis) { "Metadata metadata = MetadataBuilder.fromHeaders(request.getHeaders());" } else { "" }}
        switch(method) {
          @for(method <- service.methods) {
          case "@method.grpcName": {
            @if(method.methodType == org.apache.pekko.grpc.gen.Unary) {
            final org.apache.pekko.http.javadsl.model.HttpEntity entity = request.entity();
            if (entity instanceof org.apache.pekko.http.scaladsl.model.HttpEntity.Strict) {
              final org.apache.pekko.http.scaladsl.model.HttpEntity.Strict strictEntity =
                (org.apache.pekko.http.scaladsl.model.HttpEntity.Strict) entity;
              try {
                return GrpcMarshalling.handleUnaryResponse(
                  implementation.@{method.name}(@{method.deserializer.name}.deserialize(reader.decodeSingleFrame().apply(strictEntity.data()))@{if(powerApis) { ", metadata" } else { "" }}),
                  @method.serializer.name,
                  writer,
                  system,
                  eHandler);
              } catch (Throwable e) {
                return GrpcMarshalling.handleUnaryFailure(e, writer, system, eHandler);
              }
            } else {
              return GrpcMarshalling.handleUnaryResponse(
                GrpcMarshalling.unmarshal(request.entity(), @method.deserializer.name, mat, reader)
                  .thenCompose(e -> implementation.@{method.name}(e@{if(powerApis) { ", metadata" } else { "" }})),
                @method.serializer.name,
                writer,
                system,
                eHandler);
            }
            } else {
            response = @{method.unmarshal}(request.entity(), @method.deserializer.name, mat, reader)
              .@{if(method.outputStreaming) { "thenApply" } else { "thenCompose" }}(e -> implementation.@{method.name}(e@{if(powerApis) { ", metadata" } else { "" }}))
              .thenApply(e -> @{method.marshal}(e, @method.serializer.name, writer, system, eHandler));
            break;
            }
          }
          }
          default:
            CompletableFuture<org.apache.pekko.http.javadsl.model.HttpResponse> result = new CompletableFuture<>();
            result.completeExceptionally(new UnsupportedOperationException("Not implemented: " + method));
            response = result;
        }
        return response.exceptionally(e -> GrpcExceptionHandler.standard(e, eHandler, writer, system));
      })
      .orElseGet(() -> unsupportedMediaType);
    }
  }
}
