package polyglot;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.GeneratedMessage.Builder;
import com.google.protobuf.Message;
import com.google.protobuf.TextFormat;

import io.grpc.Channel;
import io.grpc.MethodDescriptor;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.grpc.stub.StreamObserver;
import polyglot.HelloProto.HelloRequest;
import polyglot.HelloProto.HelloResponse;
import polyglot.HelloServiceGrpc.HelloServiceBlockingStub;

public class Main {
  private static final Logger logger = LoggerFactory.getLogger(Main.class);

  private static final int REMOTE_PORT = 12345;
  private static final String REMOTE_HOST = "localhost";

  @SuppressWarnings("unchecked")  // Casting the proto request types.
  public static void main(String[] args) throws
      IOException,
      InterruptedException,
      ClassNotFoundException,
      IllegalArgumentException,
      IllegalAccessException, NoSuchMethodException, SecurityException, InvocationTargetException {
    logger.info("Starting grpc client to [" + REMOTE_HOST + ":" + REMOTE_PORT + "]");

    // Create a reusable channel.
    Channel channel = NettyChannelBuilder.forAddress(REMOTE_HOST, REMOTE_PORT)
        .negotiationType(NegotiationType.PLAINTEXT)
        .build();

    // Opening stubs the conventional way.
    HelloServiceBlockingStub helloStub = HelloServiceGrpc.newBlockingStub(channel);
    HelloResponse helloResponse = helloStub.sayHello(HelloRequest.newBuilder()
        .setRecipient("Polyglot")
        .build());
    logger.info("Got response: " + helloResponse);

    // ********** Scratch space below *************

    String className = "polyglot.HelloServiceGrpc";
    String serviceName = "polyglot.HelloService";
    String methodName = "SayHello";
    String generatedMethodName = "sayHello";

    // Inferred below.
    Class<? extends GeneratedMessage> requestType = null;
    Class<? extends GeneratedMessage> responseType = null;

    // Accessing descriptors.
    Class<?> helloServiceGrpcClass = Main.class.getClassLoader().loadClass(className);
    for (Field field : helloServiceGrpcClass.getDeclaredFields()) {
      if (MethodDescriptor.class.isAssignableFrom(field.getType())) {
        MethodDescriptor<?, ?> descriptor =
            (MethodDescriptor<?, ?>) field.get(null /* obj */); // Static field.
        logger.info("Found method descriptor: " + descriptor.getFullMethodName());

        ParameterizedType parametrizedType = (ParameterizedType) field.getGenericType();
        Type[] typeArguments = parametrizedType.getActualTypeArguments();

        logger.info("Request type: " + typeArguments[0].getTypeName());
        requestType = (Class<? extends GeneratedMessage>)
            Main.class.getClassLoader().loadClass(typeArguments[0].getTypeName());

        logger.info("Response type: " + typeArguments[1].getTypeName());
        responseType = (Class<? extends GeneratedMessage>)
            Main.class.getClassLoader().loadClass(typeArguments[1].getTypeName());
      }
    }

    // Parse the request type.
    GeneratedMessage.Builder<?> builder =
        (Builder<?>) requestType.getMethod("newBuilder").invoke(null /* obj */);
    String textFormatProto = "";
    TextFormat.getParser().merge(textFormatProto, builder);
    Message requestMessage = builder.build();

    // Opening stubs.
    Object reflectiveHelloStub = helloServiceGrpcClass
        .getMethod("newStub", io.grpc.Channel.class)
        .invoke(null /* obj */, channel);
    Method sayHelloMethod = reflectiveHelloStub.getClass()
        .getMethod(generatedMethodName, requestType, StreamObserver.class);
    //sayHelloMethod.invoke(reflectiveHelloStub, requestMessage);
  }
}

@@ -231,16 +231,64 @@
 ct.Type;
+%0Aimport java.util.concurrent.ExecutionException;
 %0A%0Aimport
@@ -339,16 +339,75 @@
 ctory;%0A%0A
+import com.google.common.util.concurrent.ListenableFuture;%0A
 import c
@@ -886,16 +886,73 @@
 ingStub;
+%0Aimport polyglot.HelloServiceGrpc.HelloServiceFutureStub;
 %0A%0Apublic
@@ -1148,16 +1148,199 @@
 host%22;%0A%0A
+  private static Channel newChannel() %7B%0A    return NettyChannelBuilder.forAddress(REMOTE_HOST, REMOTE_PORT)%0A        .negotiationType(NegotiationType.PLAINTEXT)%0A        .build();%0A  %7D%0A%0A
   @Suppr
@@ -1756,164 +1756,230 @@
  // 
-Create a reusable c
+Open a regular stub..%0A    HelloServiceBlockingStub helloStub = HelloServiceGrpc.newBlockingStub(newC
 hannel
-.
+());
 %0A    
-Channel channel = NettyChannelBuilder.forAddress(REMOTE_HOST, REMOTE_PORT)%0A        .negotiationType(NegotiationType.PLAINTEXT
+HelloResponse helloResponse = helloStub.sayHello(HelloRequest.newBuilder()%0A        .setRecipient(%22Polyglot%22
 )%0A  
@@ -1996,52 +1996,102 @@
 ld()
+)
 ;%0A
-%0A
     
-// Opening stubs the conventional way
+logger.info(%22Got response from blocking: %22 + helloResponse);%0A%0A    // Open a future stub
 .%0A  
@@ -2100,32 +2100,30 @@
 HelloService
-Blocking
+Future
 Stub helloSt
@@ -2112,32 +2112,38 @@
 FutureStub hello
+Future
 Stub = HelloServ
@@ -2157,70 +2157,106 @@
 .new
-Blocking
+Future
 Stub(
-c
+newC
 hannel
+()
 );%0A    
-HelloResponse helloResponse = hello
+ListenableFuture%3CHelloResponse%3E responseFuture =%0A        helloFuture
 Stub
@@ -2291,32 +2291,36 @@
 ilder()%0A        
+    
 .setRecipient(%22P
@@ -2329,32 +2329,36 @@
 yglot%22)%0A        
+  
+  
 .build());%0A    l
@@ -2344,32 +2344,44 @@
      .build());%0A
+    try %7B%0A  
     logger.info(
@@ -2393,37 +2393,144 @@
 response
-: %22 + helloResponse);
+ with future: %22 + responseFuture.get());%0A    %7D catch (ExecutionException e) %7B%0A      logger.error(%22Failed to make rpc%22, e);%0A    %7D
 %0A%0A    //
@@ -4433,15 +4433,20 @@
 */, 
-c
+newC
 hannel
+()
 );%0A 

Add an experiment with a futurestub.