// Copyright 2014, Kevin Ko <kevin@faveset.com>

package com.faveset.khttp;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Set;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class NonBlockingConnectionTest {
    private static final int sListenPort = 4889;

    private static class ServerThread extends Thread {
        public interface Task {
            void run(Socket sock);
        }

        private Object mSignal;
        private Task mTask;

        public ServerThread(Object signal, Task task) {
            mSignal = signal;
            mTask = task;
        }

        public void run() {
            try {
                ServerSocket listenSock = new ServerSocket(sListenPort);

                synchronized (mSignal) {
                    mSignal.notify();
                }

                Socket sock = listenSock.accept();

                mTask.run(sock);

                sock.close();
                listenSock.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private SocketChannel connect(int port) throws IOException {
        SocketAddress sa = new InetSocketAddress("127.0.0.1", port);
        return SocketChannel.open(sa);
    }

    private abstract class Tester {
        private ServerThread.Task mServerTask;
        private int mBufferSize;

        public Tester(ServerThread.Task serverTask, int bufferSize) {
            mServerTask = serverTask;
            mBufferSize = bufferSize;
        }

        protected abstract void prepareConn(NonBlockingConnection conn);

        public void run() throws IOException, InterruptedException {
            Object signal = new Object();
            ServerThread server = new ServerThread(signal, mServerTask);
            server.start();

            synchronized (signal) {
                signal.wait();
            }

            final Selector selector = Selector.open();
            SocketChannel chan = connect(sListenPort);
            NonBlockingConnection conn = new NonBlockingConnection(selector, chan, mBufferSize);

            prepareConn(conn);

            SelectionKey connKey = conn.getSelectionKey();
            while (true) {
                // We busy wait here, since we want to stop as soon as all keys
                // are cancelled.
                selector.selectNow();
                if (selector.keys().size() == 0) {
                    break;
                }

                Set<SelectionKey> readyKeys = selector.keys();

                for (SelectionKey key : readyKeys) {
                    if (key.equals(connKey)) {
                        conn.onSelect();
                    }
                }
            }

            selector.close();

            server.join();
        }
    }

    @Test
    public void test() throws IOException, InterruptedException {
        Tester tester = new Tester(new ServerThread.Task() {
            public void run(Socket sock) {
                try {
                    OutputStream os = sock.getOutputStream();

                    String data = "test";
                    os.write(data.getBytes(Helper.US_ASCII_CHARSET));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }, 1024) {
            @Override
            protected void prepareConn(NonBlockingConnection conn) {
                conn.recv(new NonBlockingConnection.OnRecvCallback() {
                    public void onRecv(NonBlockingConnection conn, ByteBuffer buf) {
                        try {
                            ByteBuffer cmpBuf = ByteBuffer.allocate(1024);

                            cmpBuf.put(buf);
                            cmpBuf.flip();
                            Helper.compare(cmpBuf, "test");

                            conn.close();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
            }
        };

        tester.run();
    }
}

@@ -144,16 +144,44 @@
 eption;%0A
+import java.io.InputStream;%0A
 import j
@@ -193,32 +193,32 @@
 o.OutputStream;%0A
-
 import java.net.
@@ -463,16 +463,49 @@
 hannel;%0A
+import java.nio.charset.Charset;%0A
 import j
@@ -673,16 +673,96 @@
 nTest %7B%0A
+    private static final Charset sUsAsciiCharset = Charset.forName(%22US-ASCII%22);%0A
     priv
@@ -3369,16 +3369,20 @@
 oid test
+Recv
 () throw
@@ -4611,16 +4611,1512 @@
     %7D;%0A%0A
+        tester.run();%0A    %7D%0A%0A    private ServerThread.Task makeRecvTask(final String expectedStr) %7B%0A        return new ServerThread.Task() %7B%0A            public void run(Socket sock) %7B%0A                try %7B%0A                    InputStream is = sock.getInputStream();%0A                    byte%5B%5D data = new byte%5BexpectedStr.length()%5D;%0A                    assertEquals(expectedStr.length(), is.read(data));%0A%0A                    String s = new String(data, sUsAsciiCharset);%0A                    assertEquals(expectedStr, s);%0A                %7D catch (IOException e) %7B%0A                    throw new RuntimeException(e);%0A                %7D%0A            %7D%0A        %7D;%0A    %7D%0A%0A    @Test%0A    public void testSend() throws IOException, InterruptedException %7B%0A        Tester tester = new Tester(makeRecvTask(%22test%22), 1024) %7B%0A            @Override%0A            protected void prepareConn(NonBlockingConnection conn) %7B%0A                ByteBuffer buf = conn.getOutBuffer();%0A%0A                String s = %22test%22;%0A                buf.put(s.getBytes(Helper.US_ASCII_CHARSET));%0A%0A                buf.flip();%0A%0A                conn.send(new NonBlockingConnection.OnSendCallback() %7B%0A                    public void onSend(NonBlockingConnection conn) %7B%0A                        try %7B%0A                            conn.close();%0A                        %7D catch (IOException e) %7B%0A                            throw new RuntimeException(e);%0A                        %7D%0A                    %7D%0A                %7D);%0A            %7D%0A        %7D;%0A%0A
         

Add send test.