package com.diguage.truman.netty;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.LockSupport;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-04-22 09:09
 */
public class Test01 {
    public static final String HOST = "127.0.0.1";
    public static final int PORT = 11911;

    @Test
    public void testServer() {
        try {
            TCPReader reader = new TCPReader(PORT);
            reader.run();
        } catch (IOException e) {
            e.printStackTrace();
        }
        LockSupport.park();

    }

    @Test
    public void testClient() {

    }
    @Test
    public void testClient2() {
        try {
            Socket socket = new Socket(HOST, PORT);
            new Thread(() -> {
                while (true) {

                    try {
                        byte[] bytes = new byte[1024];
                        int read = socket.getInputStream().read(bytes);
                        System.out.println(new String(bytes, 0, read));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }).start();
            new Thread(() -> {
                try {
                    while (true) {
                        String s = "diguage:" + LocalDateTime.now().toString();
                        socket.getOutputStream().write(s.getBytes(UTF_8));
                        Thread.sleep(1000);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
        LockSupport.park();
    }

    private static class TCPReader implements Runnable {
        private final ServerSocketChannel serverSocketChannel;
        private final Selector selector;

        public TCPReader(int port) throws IOException {
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            InetSocketAddress addr = new InetSocketAddress(port);
            serverSocketChannel.socket().bind(addr);
            serverSocketChannel.configureBlocking(false);
            SelectionKey selectionKey = serverSocketChannel.register(
                    selector, SelectionKey.OP_ACCEPT);
            selectionKey.attach(new Acceptor(selector, serverSocketChannel));
        }

        @Override
        public void run() {
            while (!Thread.interrupted()) {
                System.out.println("Waiting for new event on port: "
                        + serverSocketChannel.socket().getLocalPort());
                try {
                    if (selector.select() == 0) {
                        continue;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    dispatch(iterator.next());
                    iterator.remove();
                }
            }

        }

        private void dispatch(SelectionKey selectionKey) {
            // 这里相等于获取了上面构造函数中附加的 Acceptor 对象
            Runnable r = (Runnable) selectionKey.attachment();
            if (Objects.nonNull(r)) {
                r.run();
            }
        }
    }

    private static class Acceptor implements Runnable {
        private final ServerSocketChannel serverSocketChannel;
        private final Selector selector;

        public Acceptor(Selector selector, ServerSocketChannel ssc) {
            this.serverSocketChannel = ssc;
            this.selector = selector;
        }

        @Override
        public void run() {
            try {
                SocketChannel socketChannel = serverSocketChannel.accept();
                System.out.println(
                        socketChannel.socket().getRemoteSocketAddress()
                                .toString() + " is connected.");
                if (Objects.nonNull(socketChannel)) {
                    socketChannel.configureBlocking(false);
                    SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
                    selector.wakeup();
                    selectionKey.attach(new TCPHandler(selectionKey, socketChannel));
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static class TCPHandler implements Runnable {

        private final SelectionKey selectionKey;
        private final SocketChannel socketChannel;

        private int state;

        public TCPHandler(SelectionKey selectionKey, SocketChannel socketChannel) {
            this.selectionKey = selectionKey;
            this.socketChannel = socketChannel;
            this.state = 0;
        }

        @Override
        public void run() {
            try {
                if (state == 0) {
                    read();
                } else {
                    send();
                }
            } catch (IOException e) {
                System.out.println("[Warning!] A client has been closed.");
                closeChannel();
            }
        }

        private void closeChannel() {
            try {
                selectionKey.cancel();
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void read() throws IOException {
            byte[] bytes = new byte[1024];
            ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
            int numBytes = socketChannel.read(byteBuffer);
            if (numBytes == -1) {
                System.out.println("[Warning!] A client has bean closed.");
                closeChannel();
                return;
            }
            String str = new String(bytes, 0, numBytes);
            if (!"".equals(str)) {
                process(str);
                System.out.println(
                        socketChannel.socket().getRemoteSocketAddress()
                                .toString() + " > " + str);
                state = 1;
                selectionKey.interestOps(SelectionKey.OP_WRITE);
                selectionKey.selector().wakeup();
            }
        }

        private void send() throws IOException {
        }

        private void process(String str) {

        }
    }


    private void readClientDate(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
        int read = socketChannel.read(byteBuffer);
        if (read > 0) {
            String s = new String(byteBuffer.array());
            writeClientData(socketChannel, s);
            System.out.printf("%8d, %s", byteBuffer.position(), s);
        }
    }

    private void writeClientData(SocketChannel socketChannel, String s) {
    }
}