package com.github.ompc.laser.server;

import com.github.ompc.laser.common.LaserOptions;
import com.github.ompc.laser.common.channel.CompressWritableByteChannel;
import com.github.ompc.laser.common.datasource.DataSource;
import com.github.ompc.laser.common.datasource.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;

import static com.github.ompc.laser.common.LaserConstant.*;
import static com.github.ompc.laser.common.SocketUtils.format;
import static java.lang.Thread.currentThread;

/**
 * Nio实现的服务端
 * Created by vlinux on 14-10-4.
 */
public class NioLaserServer {

    private final Logger log = LoggerFactory.getLogger(getClass());

    private final DataSource dataSource;
    private final ExecutorService executorService;
    private final ServerConfiger configer;
    private final LaserOptions options;

    private ServerSocketChannel serverSocketChannel;
    private volatile boolean isRunning = true;
    private boolean isReaderRunning = true;
    private boolean isWriterRunning = true;

    public NioLaserServer(DataSource dataSource, ExecutorService executorService, ServerConfiger configer, LaserOptions options) {
        this.dataSource = dataSource;
        this.executorService = executorService;
        this.configer = configer;
        this.options = options;
    }


    final Runnable accepter = new Runnable() {

        @Override
        public void run() {

            currentThread().setName("server-accepter");
            try (final Selector selector = Selector.open()) {

                serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                while (isRunning) {

                    selector.select();
                    final Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                    while (iter.hasNext()) {
                        final SelectionKey key = iter.next();
                        iter.remove();

                        if (key.isAcceptable()) {
                            final SocketChannel socketChannel = serverSocketChannel.accept();
                            configSocketChannel(socketChannel);
                            new ChildHandler(socketChannel);
                            log.info("{} was connected.", format(socketChannel.socket()));
                        }

                    }//while

                }

            } catch (IOException ioe) {
                log.warn("server[port={}] accept failed.", configer.getPort(), ioe);
            }

        }

    };


    /**
     * Child处理器
     */
    private class ChildHandler {

        private final SocketChannel socketChannel;
        private final AtomicInteger reqCounter = new AtomicInteger(0);

        private ChildHandler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
            executorService.execute(childReader);
            executorService.execute(childWriter);
        }

        final Runnable childReader = new Runnable() {

            @Override
            public void run() {

                currentThread().setName("child-" + format(socketChannel.socket()) + "-reader");

                final ByteBuffer buffer = ByteBuffer.allocateDirect(options.getServerChildReceiverBufferSize());
                try (final Selector selector = Selector.open()) {

                    socketChannel.register(selector, SelectionKey.OP_READ);
                    while (isRunning
                            && isReaderRunning) {

                        selector.select();
                        final Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                        while (iter.hasNext()) {
                            final SelectionKey key = iter.next();
                            iter.remove();

                            if (key.isReadable()) {

                                socketChannel.read(buffer);
                                buffer.flip();

                                while (true) {
                                    if (buffer.remaining() < Integer.BYTES) {
                                        break;
                                    }

                                    final int type = buffer.getInt();
                                    if (type != PRO_REQ_GETDATA) {
                                        throw new IOException("decode failed, illegal type=" + type);
                                    }

                                    reqCounter.incrementAndGet();

                                }//while
                                buffer.compact();


                            }//if:readable

                        }//while:iter

                    }//while:MAIN_LOOP

                } catch (IOException ioe) {
                    log.info("{} was disconnect for read.", format(socketChannel.socket()));
                } finally {
                    isReaderRunning = false;
                }

            }

        };

        final Runnable childWriter = new Runnable() {

            @Override
            public void run() {

                currentThread().setName("child-" + format(socketChannel.socket()) + "-writer");
                currentThread().setPriority(Thread.MAX_PRIORITY);

                final ByteBuffer buffer = ByteBuffer.allocateDirect(options.getServerChildSendBufferSize());
                final WritableByteChannel writableByteChannel = options.isEnableCompress()
                        ? new CompressWritableByteChannel(socketChannel, options.getCompressSize())
                        : socketChannel;

                boolean isEOF = false;
                final Row row = new Row();
                try (final Selector selector = Selector.open()) {

                    final int LIMIT_REMAINING = 212;//TYPE(4B)+LINENUM(4B)+LEN(4B)+DATA(200B)
                    socketChannel.register(selector, SelectionKey.OP_WRITE);

                    DecodeState state = DecodeState.FILL_BUFF;
                    boolean isNeedSend = false;
                    while (isRunning
                            && isWriterRunning) {

                        switch (state) {

                            case FILL_BUFF: {

                                // 一进来就先判断是否到达了EOF,如果已经到达了则不需要访问数据源
                                if (isEOF) {
                                    reqCounter.decrementAndGet();
                                    buffer.putInt(PRO_RESP_GETEOF);
                                    isNeedSend = true;
                                } else {

                                    if (reqCounter.get() > 0) {
                                        reqCounter.decrementAndGet();
                                        dataSource.getRow(row);

                                        if (row.getLineNum() < 0) {
                                            buffer.putInt(PRO_RESP_GETEOF);
                                            isEOF = true;
                                            isNeedSend = true;
                                        } else {
                                            buffer.putInt(PRO_RESP_GETDATA);
                                            buffer.putInt(row.getLineNum());

                                            buffer.putInt(row.getData().length);
                                            buffer.put(row.getData());

                                            if (buffer.remaining() < LIMIT_REMAINING) {
                                                // TODO : 目前这里利用了DATA长度不超过200的限制,没有足够的通用性,后续改掉
                                                isNeedSend = true;
                                            }
                                        }
                                    }

                                }

                                // 前边层层处理之后是否需要发送
                                if (isNeedSend) {
                                    buffer.flip();
                                    state = DecodeState.SEND_BUFF;
                                    isNeedSend = false;
                                }
                                break;
                            }


                            case SEND_BUFF: {

                                selector.select();
                                final Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                                while (iter.hasNext()) {
                                    final SelectionKey key = iter.next();
                                    iter.remove();

                                    if (key.isWritable()) {
                                        while (buffer.hasRemaining()) {
//                                            if( writableByteChannel instanceof CompressWritableByteChannel ) {
//                                                ((CompressWritableByteChannel)writableByteChannel).write(buffer,isEOF);
//                                            } else {
//                                                writableByteChannel.write(buffer);
//                                            }
                                            writableByteChannel.write(buffer);
                                        }
                                        buffer.compact();
                                        state = DecodeState.FILL_BUFF;

//                                        if (!buffer.hasRemaining()) {
//                                            // 缓存中的内容发送完之后才跳转到填充
//                                            state = DecodeState.FILL_BUFF;
//                                            buffer.compact();
//                                        }

                                    }

                                }//while:iter

                                break;
                            }

                        }//switch:state

                    }//while:MAIN_LOOP

                } catch (IOException ioe) {
                    log.info("{} was disconnect for write.", format(socketChannel.socket()));
                } finally {
                    isWriterRunning = false;
                }

            }

        };

    }


    /**
     * 获取并配置ServerSocketChannel
     *
     * @return 返回配置好的ServerSocketChannel
     * @throws IOException
     */
    private ServerSocketChannel getServerSocketChannel() throws IOException {
        final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().setSoTimeout(options.getServerSocketTimeout());
        return serverSocketChannel;
    }

    /**
     * 配置SocketChannel
     *
     * @throws IOException
     */
    private void configSocketChannel(SocketChannel socketChannel) throws IOException {
        socketChannel.configureBlocking(false);
        // config the socket
        final Socket socket = socketChannel.socket();
        socket.setTcpNoDelay(options.isServerChildTcpNoDelay());
        socket.setReceiveBufferSize(options.getServerChildSocketReceiverBufferSize());
        socket.setSendBufferSize(options.getServerChildSocketSendBufferSize());
        socket.setSoTimeout(options.getServerChildSocketTimeout());
        socket.setPerformancePreferences(
                options.getServerChildPerformancePreferences()[0],
                options.getServerChildPerformancePreferences()[1],
                options.getServerChildPerformancePreferences()[2]);
        socket.setTrafficClass(options.getServerChildTrafficClass());
    }

    /**
     * 启动服务端
     *
     * @throws IOException
     */
    public void startup() throws IOException {

        serverSocketChannel = getServerSocketChannel();
        serverSocketChannel.bind(new InetSocketAddress(configer.getPort()), options.getServerBacklog());

        executorService.execute(accepter);
        log.info("server[port={}] startup successed.", configer.getPort());
    }

    /**
     * 关闭服务端
     *
     * @throws IOException
     */
    public void shutdown() throws IOException {

        isRunning = false;
        if (null != serverSocketChannel) {
            serverSocketChannel.close();
        }

        log.info("server[port={}] shutdown successed.", configer.getPort());

    }


    /**
     * 发送数据解码
     */
    enum DecodeState {
        FILL_BUFF,
        SEND_BUFF
    }

}