/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.ipc;

import java.io.IOException;
import java.net.InetSocketAddress;

import org.junit.Assert;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.TestProtoBufRpc.PBServerImpl;
import org.apache.hadoop.ipc.TestProtoBufRpc.TestRpcService;
import org.apache.hadoop.ipc.protobuf.TestRpcServiceProtos.TestProtobufRpcProto;
import org.apache.hadoop.net.NetUtils;
import org.junit.Before;
import org.junit.After;
import org.junit.Test;
import com.google.protobuf.BlockingService;

public class TestMultipleProtocolServer {
  private static final String ADDRESS = "0.0.0.0";
  private static InetSocketAddress addr;
  private static RPC.Server server;

  private static Configuration conf = new Configuration();
  
  
  @ProtocolInfo(protocolName="Foo")
  interface Foo0 extends VersionedProtocol {
    public static final long versionID = 0L;
    String ping() throws IOException;
    
  }
  
  @ProtocolInfo(protocolName="Foo")
  interface Foo1 extends VersionedProtocol {
    public static final long versionID = 1L;
    String ping() throws IOException;
    String ping2() throws IOException;
  }
  
  @ProtocolInfo(protocolName="Foo")
  interface FooUnimplemented extends VersionedProtocol {
    public static final long versionID = 2L;
    String ping() throws IOException;  
  }
  
  interface Mixin extends VersionedProtocol{
    public static final long versionID = 0L;
    void hello() throws IOException;
  }
  interface Bar extends Mixin {
    public static final long versionID = 0L;
    int echo(int i) throws IOException;
  }
  
  
  
  class Foo0Impl implements Foo0 {

    @Override
    public long getProtocolVersion(String protocol, long clientVersion)
        throws IOException {
      return Foo0.versionID;
    }

    @SuppressWarnings("unchecked")
    @Override
    public ProtocolSignature getProtocolSignature(String protocol,
        long clientVersion, int clientMethodsHash) throws IOException {
      Class<? extends VersionedProtocol> inter;
      try {
        inter = (Class<? extends VersionedProtocol>)getClass().
                                          getGenericInterfaces()[0];
      } catch (Exception e) {
        throw new IOException(e);
      }
      return ProtocolSignature.getProtocolSignature(clientMethodsHash, 
          getProtocolVersion(protocol, clientVersion), inter);
    }

    @Override
    public String ping() {
      return "Foo0";     
    }
    
  }
  
  class Foo1Impl implements Foo1 {

    @Override
    public long getProtocolVersion(String protocol, long clientVersion)
        throws IOException {
      return Foo1.versionID;
    }

    @SuppressWarnings("unchecked")
    @Override
    public ProtocolSignature getProtocolSignature(String protocol,
        long clientVersion, int clientMethodsHash) throws IOException {
      Class<? extends VersionedProtocol> inter;
      try {
        inter = (Class<? extends VersionedProtocol>)getClass().
                                        getGenericInterfaces()[0];
      } catch (Exception e) {
        throw new IOException(e);
      }
      return ProtocolSignature.getProtocolSignature(clientMethodsHash, 
          getProtocolVersion(protocol, clientVersion), inter);
    }

    @Override
    public String ping() {
      return "Foo1";
    }

    @Override
    public String ping2() {
      return "Foo1";
      
    }
    
  }

  
  class BarImpl implements Bar {

    @Override
    public long getProtocolVersion(String protocol, long clientVersion)
        throws IOException {
      return Bar.versionID;
    }

    @SuppressWarnings("unchecked")
    @Override
    public ProtocolSignature getProtocolSignature(String protocol,
        long clientVersion, int clientMethodsHash) throws IOException {
      Class<? extends VersionedProtocol> inter;
      try {
        inter = (Class<? extends VersionedProtocol>)getClass().
                                          getGenericInterfaces()[0];
      } catch (Exception e) {
        throw new IOException(e);
      }
      return ProtocolSignature.getProtocolSignature(clientMethodsHash, 
          getProtocolVersion(protocol, clientVersion), inter);
    }

    @Override
    public int echo(int i) {
      return i;
    }

    @Override
    public void hello() {

      
    }
  }
  @Before
  public void setUp() throws Exception {
    // create a server with two handlers
    server = new RPC.Builder(conf).setProtocol(Foo0.class)
        .setInstance(new Foo0Impl()).setBindAddress(ADDRESS).setPort(0)
        .setNumHandlers(2).setVerbose(false).build();
    server.addProtocol(RPC.RpcKind.RPC_WRITABLE, Foo1.class, new Foo1Impl());
    server.addProtocol(RPC.RpcKind.RPC_WRITABLE, Bar.class, new BarImpl());
    server.addProtocol(RPC.RpcKind.RPC_WRITABLE, Mixin.class, new BarImpl());
    
    
    // Add Protobuf server
    // Create server side implementation
    PBServerImpl pbServerImpl = 
        new PBServerImpl();
    BlockingService service = TestProtobufRpcProto
        .newReflectiveBlockingService(pbServerImpl);
    server.addProtocol(RPC.RpcKind.RPC_PROTOCOL_BUFFER, TestRpcService.class,
        service);
    server.start();
    addr = NetUtils.getConnectAddress(server);
  }
  
  @After
  public void tearDown() throws Exception {
    server.stop();
  }

  @Test
  public void test1() throws IOException {
    ProtocolProxy<?> proxy;
    proxy = RPC.getProtocolProxy(Foo0.class, Foo0.versionID, addr, conf);

    Foo0 foo0 = (Foo0)proxy.getProxy(); 
    Assert.assertEquals("Foo0", foo0.ping());
    
    
    proxy = RPC.getProtocolProxy(Foo1.class, Foo1.versionID, addr, conf);
    
    
    Foo1 foo1 = (Foo1)proxy.getProxy(); 
    Assert.assertEquals("Foo1", foo1.ping());
    Assert.assertEquals("Foo1", foo1.ping());
    
    
    proxy = RPC.getProtocolProxy(Bar.class, Foo1.versionID, addr, conf);
    
    
    Bar bar = (Bar)proxy.getProxy(); 
    Assert.assertEquals(99, bar.echo(99));
    
    // Now test Mixin class method
    
    Mixin mixin = bar;
    mixin.hello();
  }
  
  
  // Server does not implement the FooUnimplemented version of protocol Foo.
  // See that calls to it fail.
  @Test(expected=IOException.class)
  public void testNonExistingProtocol() throws IOException {
    ProtocolProxy<?> proxy;
    proxy = RPC.getProtocolProxy(FooUnimplemented.class, 
        FooUnimplemented.versionID, addr, conf);

    FooUnimplemented foo = (FooUnimplemented)proxy.getProxy(); 
    foo.ping();
  }
  
  
  /**
   * getProtocolVersion of an unimplemented version should return highest version
   * Similarly getProtocolSignature should work.
   * @throws IOException
   */
  @Test
  public void testNonExistingProtocol2() throws IOException {
    ProtocolProxy<?> proxy;
    proxy = RPC.getProtocolProxy(FooUnimplemented.class, 
        FooUnimplemented.versionID, addr, conf);

    FooUnimplemented foo = (FooUnimplemented)proxy.getProxy(); 
    Assert.assertEquals(Foo1.versionID, 
        foo.getProtocolVersion(RPC.getProtocolName(FooUnimplemented.class), 
        FooUnimplemented.versionID));
    foo.getProtocolSignature(RPC.getProtocolName(FooUnimplemented.class), 
        FooUnimplemented.versionID, 0);
  }
  
  @Test(expected=IOException.class)
  public void testIncorrectServerCreation() throws IOException {
    new RPC.Builder(conf).setProtocol(Foo1.class).setInstance(new Foo0Impl())
        .setBindAddress(ADDRESS).setPort(0).setNumHandlers(2).setVerbose(false)
        .build();
  } 
  
  // Now test a PB service - a server  hosts both PB and Writable Rpcs.
  @Test
  public void testPBService() throws Exception {
    // Set RPC engine to protobuf RPC engine
    Configuration conf2 = new Configuration();
    RPC.setProtocolEngine(conf2, TestRpcService.class,
        ProtobufRpcEngine.class);
    TestRpcService client = RPC.getProxy(TestRpcService.class, 0, addr, conf2);
    TestProtoBufRpc.testProtoBufRpc(client);
  }
}
