package imcServer.contract;

import Utils.IoUtils.IoUtils;
import imcCore.contract.Exceptions.NotContractMethodException;
import imcCore.contract.ImcClass;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.Executors;

class PersistentContractImplRunner<T> extends ContractImplRunners<T> {
    private final AsynchronousServerSocketChannel asynchronousServerSocketChannel;
    private final AsynchronousChannelGroup asynchChannelGroup;

    PersistentContractImplRunner(T impl, ImcClass imcClass, int port) throws IOException {
        super(impl, imcClass, port);
        asynchChannelGroup = AsynchronousChannelGroup.withThreadPool(Executors.newSingleThreadExecutor());
        asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open(asynchChannelGroup).bind(new InetSocketAddress(port));
    }

    @Override
    void startServerAsync() {
        startServer();
    }

    @Override
    void startServer() {
        if (asynchronousServerSocketChannel.isOpen()) {
            asynchronousServerSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                @Override
                public void completed(AsynchronousSocketChannel asyncSocketChannel, Object attachment) {
                    if (asynchronousServerSocketChannel.isOpen()) {
                        asynchronousServerSocketChannel.accept(null, this);
                    }
                    handleNewClient(asyncSocketChannel);
                }

                @Override
                public void failed(Throwable exc, Object attachment) {
                    if (asynchronousServerSocketChannel.isOpen()) {
                        //TODO print to log
                        asynchronousServerSocketChannel.accept(null, this);
                    }
                }
            });
        }
    }

    @Override
    boolean isServerRun() {
        return asynchronousServerSocketChannel.isOpen();
    }

    private void handleNewClient(AsynchronousSocketChannel asynchronousSocketChannel) {
        connectClient(asynchronousSocketChannel);
    }

    private void connectClient(AsynchronousSocketChannel asynchronousSocketChannel) {
        IoUtils.write(asynchronousSocketChannel, getVersion(), (bytes, integer) -> IoUtils.read(asynchronousSocketChannel, INT_SIZE, (bytes1, integer1) -> {
            if (integer1 == INT_SIZE) {
                int version = handShake(bytesToInt(bytes1));
                initConnect(asynchronousSocketChannel);
            }
        }, (bytes1, integer1, throwable) -> {
            //TODO print to log
        }));
    }

    private void initConnect(AsynchronousSocketChannel asynchronousSocketChannel) {
        IoUtils.write(asynchronousSocketChannel, getServerConf(), (bytes, integer) -> waitForInvoke(asynchronousSocketChannel));
    }

    private void waitForInvoke(AsynchronousSocketChannel asynchronousSocketChannel) {
        IoUtils.read(asynchronousSocketChannel, INT_SIZE, (bytes, integer) -> {
            if (integer == INT_SIZE) {
                int bufSize = bytesToInt(bytes);
                if (bufSize > 0) {
                    readMethodInvokeBuf(asynchronousSocketChannel, bufSize);
                } else {
                    waitForInvoke(asynchronousSocketChannel);
                }
            }
        }, (bytes, integer, throwable) -> {
            //TODO print to log
        });
    }

    private void readMethodInvokeBuf(AsynchronousSocketChannel asynchronousSocketChannel, int bufSize) {
        IoUtils.read(asynchronousSocketChannel, bufSize, (bytes, integer) -> {
            if (integer == bufSize) {
                try {
                    byte[] sendBuf = invokeMethod(bytes);
                    if (sendBuf != null) {
                        IoUtils.write(asynchronousSocketChannel, intToBytes(sendBuf.length), (bytes1, integer1) ->
                                IoUtils.write(asynchronousSocketChannel, sendBuf, (bytes2, integer2) ->
                                        waitForInvoke(asynchronousSocketChannel)));
                    } else {
                        waitForInvoke(asynchronousSocketChannel);
                    }
                } catch (IllegalAccessException | IOException | InstantiationException | InvocationTargetException | NoSuchMethodException | NotContractMethodException e) {
                    //TODO print to log
                    e.printStackTrace();
                    waitForInvoke(asynchronousSocketChannel);
                }
            }
        }, (bytes, integer, throwable) -> {
            //TODO print to log
        });
    }

    private byte[] getServerConf() {
        return new byte[]{1};
    }

    @Override
    void stopRunner() {
        try {
            asynchronousServerSocketChannel.close();
            asynchChannelGroup.shutdown();
        } catch (IOException e) {
            //TODO print to log
            e.printStackTrace();
        }
    }
}

@@ -2875,16 +2875,92 @@
       %7D)
+, (bytes, integer, throwable) -%3E %7B%0A            //TODO print to log%0A        %7D
 );%0A    %7D
@@ -3171,16 +3171,92 @@
 Channel)
+, (bytes, integer, throwable) -%3E %7B%0A            //TODO print to log%0A        %7D
 );%0A    %7D
@@ -4481,17 +4481,253 @@
 Channel)
-)
+, (bytes2, integer2, throwable) -%3E %7B%0A                                    //TODO print to log%0A                                %7D), (bytes1, integer1, throwable) -%3E %7B%0A                            //TODO print to log%0A                        %7D
 );%0A     

fix small bug when the client disconnected fourcly.