/*
 * Copyright 2015 the original author or authors.
 *
 * Licensed 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 io.atomix.catalyst.transport.netty;

import io.atomix.catalyst.concurrent.SingleThreadContext;
import io.atomix.catalyst.concurrent.ThreadContext;
import io.atomix.catalyst.serializer.SerializationException;
import io.atomix.catalyst.serializer.Serializer;
import io.atomix.catalyst.transport.Address;
import io.atomix.catalyst.transport.Client;
import io.atomix.catalyst.transport.Server;
import io.atomix.catalyst.transport.Transport;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;

import net.jodah.concurrentunit.ConcurrentTestCase;

import org.testng.annotations.Test;
/**
 * Netty transport test.
 *
 * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
 */
@Test
public class NettyTransportTest extends ConcurrentTestCase {

  /**
   * Tests connecting to a server and sending a message.
   */
  public void testSendReceive() throws Throwable {
    Properties properties = new Properties();
    NettyOptions nettyProperties = new NettyOptions(properties);

    Transport transport = new NettyTransport(nettyProperties);

    Server server = transport.server();
    Client client = transport.client();

    ThreadContext context = new SingleThreadContext("test-thread-%d", new Serializer());

    context.executor().execute(() -> {
      try {
        server.listen(new Address(new InetSocketAddress(InetAddress.getByName("localhost"), 5555)), connection -> {
          connection.<String, String>handler(String.class, message -> {
            threadAssertEquals("Hello world!", message);
            return CompletableFuture.completedFuture("Hello world back!");
          });
        }).thenRun(this::resume);
      } catch (UnknownHostException e) {
        threadFail(e);
      }
    });
    await(1000);

    context.executor().execute(() -> {
      try {
        client.connect(new Address(new InetSocketAddress(InetAddress.getByName("localhost"), 5555))).thenAccept(connection -> {
          connection.sendAndReceive("Hello world!").thenAccept(response -> {
            threadAssertEquals("Hello world back!", response);
            resume();
          });
        });
      } catch (UnknownHostException e) {
        threadFail(e);
      }
    });
    await(1000);

    context.executor().execute(() -> {
      client.close().thenRun(this::resume);
      server.close().thenRun(this::resume);
    });
    await(1000, 2);
  }

  /**
   * Tests failing a message that's not serializable.
   */
  public void testFailNotSerializable() throws Throwable {
    Transport transport = NettyTransport.builder().withThreads(2).build();

    Server server = transport.server();
    Client client = transport.client();

    ThreadContext context = new SingleThreadContext("test-thread-%d", new Serializer());

    context.executor().execute(() -> {
      try {
        server.listen(new Address(new InetSocketAddress(InetAddress.getByName("localhost"), 5556)), connection -> {
          connection.<String, String>handler(String.class, message -> {
            threadAssertEquals("Hello world!", message);
            return CompletableFuture.completedFuture("Hello world back!");
          });
        }).thenRun(this::resume);
      } catch (UnknownHostException e) {
        threadFail(e);
      }
    });
    await(1000);

    context.executor().execute(() -> {
      try {
        client.connect(new Address(new InetSocketAddress(InetAddress.getByName("localhost"), 5556))).thenAccept(connection -> {
          connection.sendAndReceive(new NotSerializable()).whenComplete((result, error) -> {
            threadAssertNotNull(error);
            threadAssertTrue(error instanceof SerializationException);
            resume();
          });
        });
      } catch (UnknownHostException e) {
        threadFail(e);
      }
    });
    await(1000);

    context.executor().execute(() -> {
      client.close().thenRun(this::resume);
      server.close().thenRun(this::resume);
    });
    await(1000, 2);
  }

  @Test(enabled=false)
  // This test fails because of cert validation
  // Keeping it in here for own testing and as an example
  public void testSendReceiveSSL() throws Throwable {
    Properties properties = new Properties();
    properties.put(NettyOptions.SSL_ENABLED, "true");
    properties.put(NettyOptions.SSL_KEY_STORE_PATH, "src/test/resources/test.keystore");
    properties.put(NettyOptions.SSL_KEY_STORE_PASSWORD, "password");
    NettyOptions nettyProperties = new NettyOptions(properties);

    Transport transport = new NettyTransport(nettyProperties);

    Server server = transport.server();
    Client client = transport.client();

    ThreadContext context = new SingleThreadContext("test-thread-%d", new Serializer());

    context.executor().execute(() -> {
      try {
        server.listen(new Address(new InetSocketAddress(InetAddress.getByName("localhost"), 5555)), connection -> {
          connection.<String, String>handler(String.class, message -> {
            threadAssertEquals("Hello world!", message);
            return CompletableFuture.completedFuture("Hello world back!");
          });
        }).thenRun(this::resume);
      } catch (UnknownHostException e) {
        threadFail(e);
      }
    });
    await(1000);

    context.executor().execute(() -> {
      try {
        client.connect(new Address(new InetSocketAddress(InetAddress.getByName("localhost"), 5555))).thenAccept(connection -> {
          connection.send("Hello world!").thenAccept(response -> {
            threadAssertEquals("Hello world back!", response);
            resume();
          });
        });
      } catch (UnknownHostException e) {
        threadFail(e);
      }
    });
    await(1000);

    context.executor().execute(() -> {
      client.close().thenRun(this::resume);
      server.close().thenRun(this::resume);
    });
    await(1000, 2);
  }

  /**
   * Tests class that's not serializable.
   */
  private static class NotSerializable {
  }

}

@@ -2460,35 +2460,36 @@
 ;%0A    await(1000
+0
 );%0A
-
 %0A    context.exe
@@ -2926,32 +2926,33 @@
 ;%0A    await(1000
+0
 );%0A%0A    context.
@@ -3080,32 +3080,33 @@
 ;%0A    await(1000
+0
 , 2);%0A  %7D%0A%0A  /**
@@ -3977,32 +3977,33 @@
 ;%0A    await(1000
+0
 );%0A%0A    context.
@@ -4507,32 +4507,33 @@
 ;%0A    await(1000
+0
 );%0A%0A    context.
@@ -4661,32 +4661,33 @@
 ;%0A    await(1000
+0
 , 2);%0A  %7D%0A%0A  @Te
@@ -5928,32 +5928,33 @@
 ;%0A    await(1000
+0
 );%0A%0A    context.
@@ -6392,16 +6392,17 @@
 ait(1000
+0
 );%0A%0A    
@@ -6524,32 +6524,32 @@
 esume);%0A    %7D);%0A
-
     await(1000, 
@@ -6546,16 +6546,17 @@
 ait(1000
+0
 , 2);%0A  

Increase Netty test timeouts.