/*
 * Copyright (c) IBM Corporation 2017. All Rights Reserved.
 * Project name: java-async-util
 * This project is licensed under the Apache License 2.0, see LICENSE.
 */

package com.ibm.asyncutil.examples.nio;

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import com.ibm.asyncutil.util.StageSupport;

/**
 * Static methods to convert the callback based {@link java.nio.channels.AsynchronousChannel} API to
 * one based on {@link CompletionStage}
 */
public class NioBridge {

  static CompletionStage<AsynchronousSocketChannel> accept(
      final AsynchronousServerSocketChannel server) {
    final CompletableFuture<AsynchronousSocketChannel> channelFuture = new CompletableFuture<>();
    server.accept(channelFuture,
        new CompletionHandler<AsynchronousSocketChannel, CompletableFuture<AsynchronousSocketChannel>>() {
          @Override
          public void completed(final AsynchronousSocketChannel result,
              final CompletableFuture<AsynchronousSocketChannel> attachment) {
            attachment.complete(result);
          }

          @Override
          public void failed(final Throwable exc,
              final CompletableFuture<AsynchronousSocketChannel> attachment) {
            attachment.completeExceptionally(exc);
          }
        });
    return channelFuture;
  }

  static CompletionStage<AsynchronousSocketChannel> connect(final SocketAddress addr) {
    try {
      final AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
      return connect(channel, addr).thenApply(ig -> channel);
    } catch (final IOException e) {
      return StageSupport.exceptionalStage(e);
    }
  }

  static CompletionStage<Void> connect(final AsynchronousSocketChannel channel,
      final SocketAddress addr) {
    final CompletableFuture<Void> connectFuture = new CompletableFuture<>();
    channel.connect(addr, connectFuture, new CompletionHandler<Void, CompletableFuture<Void>>() {
      @Override
      public void completed(final Void result, final CompletableFuture<Void> attachment) {
        attachment.complete(null);
      }

      @Override
      public void failed(final Throwable exc, final CompletableFuture<Void> attachment) {
        attachment.completeExceptionally(exc);
      }
    });
    return connectFuture;
  }

  static CompletionStage<Void> writeInt(final AsynchronousSocketChannel channel,
      final int toWrite) {
    final ByteBuffer buffer = ByteBuffer.allocate(4);
    buffer.putInt(toWrite);
    buffer.flip();
    final CompletableFuture<Void> writeFuture = new CompletableFuture<>();
    channel.write(buffer, writeFuture,
        new CompletionHandler<Integer, CompletableFuture<Void>>() {
          @Override
          public void completed(final Integer result, final CompletableFuture<Void> attachment) {
            if (result != 4)
              attachment.completeExceptionally(new IOException("write interrupted"));
            else
              attachment.complete(null);
          }

          @Override
          public void failed(final Throwable exc, final CompletableFuture<Void> attachment) {
            attachment.completeExceptionally(exc);
          }
        });
    return writeFuture;
  }

  static CompletionStage<Integer> readInt(final AsynchronousSocketChannel channel) {
    final ByteBuffer buf = ByteBuffer.allocate(4);
    final CompletableFuture<Integer> intFuture = new CompletableFuture<>();
    channel.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {
      @Override
      public void completed(final Integer result, final ByteBuffer attachment) {
        attachment.flip();
        intFuture.complete(attachment.getInt());
      }

      @Override
      public void failed(final Throwable exc, final ByteBuffer attachment) {
        intFuture.completeExceptionally(exc);
      }
    });
    return intFuture;
  }

  public static void main(final String[] args) throws IOException {
    final AsynchronousServerSocketChannel server =
        AsynchronousServerSocketChannel.open().bind(null);

    final CompletionStage<AsynchronousSocketChannel> acceptStage = accept(server);
    final SocketAddress addr = server.getLocalAddress();
    final CompletionStage<AsynchronousSocketChannel> connectStage = connect(addr);

    // after connecting, write the integer 42 to the server
    final CompletionStage<Void> writeStage =
        connectStage.thenAccept(channel -> writeInt(channel, 42));

    final CompletionStage<Void> readStage = acceptStage
        // after accepting, read an int from the socket
        .thenCompose(NioBridge::readInt)
        // print the result
        .thenAccept(System.out::println);

    // wait for the write and the read to complete
    writeStage.toCompletableFuture().join();
    readStage.toCompletableFuture().join();
  }

}