package net.northking.cctp.upperComputer.driver.ios.packet;


import net.northking.cctp.upperComputer.utils.ByteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 指令数据包传送
 * <br>
 * 提供同步和异步发送方法，不包含Socket创建
 */
public class PacketTransfer {

    private final Logger logger = LoggerFactory.getLogger(PacketTransfer.class);

    /**
     * 同步请求超时响应时间，单位：秒
     */
    public static final int SYNC_TIMEOUT = 3600;

    private final AtomicInteger autoRequestId = new AtomicInteger();

    private final Socket socket;

    private final DataInputStream inputStream;

    private final DataOutputStream outputStream;

    private final SendHandler sendHandler;

    private final ReceiveHandler receiveHandler;

    private final AsyncPacketHandler asyncPacketHandler;

    private final ArrayBlockingQueue<SendTarget> sendQueue = new ArrayBlockingQueue<>(20);

    private final ArrayList<SendTarget> syncWaitResponseTargetList = new ArrayList<>();

    private final ArrayList<AsyncPacketListener> asyncPacketListeners = new ArrayList<>();

    /**
     * 是否已连接
     */
    private boolean connected;

    /**
     * 连接断开监听
     */
    private Runnable onDisconnectListener = null;


    /**
     * 一个TAG，用于区分多个PacketTransfer
     */
    private final String TAG;

    public PacketTransfer(Socket socket, String tag) throws IOException {
        this.socket = socket;
        this.inputStream = new DataInputStream(socket.getInputStream());
        this.outputStream = new DataOutputStream(socket.getOutputStream());
        this.TAG = tag;
        this.sendHandler = new SendHandler();
        this.receiveHandler = new ReceiveHandler();
        this.asyncPacketHandler = new AsyncPacketHandler();
        this.connected = true;
    }


    public PacketTransfer(InputStream inputStream, OutputStream outputStream, String tag) {
        this(null, inputStream, outputStream, tag);
    }

    public PacketTransfer(Socket socket, InputStream inputStream, OutputStream outputStream, String tag) {
        this.socket = socket;
        this.inputStream = new DataInputStream(inputStream);
        this.outputStream = new DataOutputStream(outputStream);
        this.TAG = tag;
        this.sendHandler = new SendHandler();
        this.receiveHandler = new ReceiveHandler();
        this.asyncPacketHandler = new AsyncPacketHandler();
        this.connected = true;
    }

    public ICommandPacket syncSend(int commandId) {
        return syncSend(commandId, new EmptyCommandData());
    }

    public ICommandPacket syncSend(int commandId, ICommandData data) {
        return syncSend(commandId, autoRequestId.getAndIncrement(), data);
    }

    public ICommandPacket syncSend(int commandId, int requestId, ICommandData data) {
        ICommandPacket sendPacket;
        try {
            sendPacket = buildPacketFromData(commandId, requestId, data);
        } catch (IOException e) {
            logger.error("序列化数据时发生错误", e);
            return null;
        }

        return syncSend(sendPacket);
    }

    public void asyncSend(int commandId) {
        asyncSend(commandId, new EmptyCommandData());
    }

    public void asyncSend(int commandId, ICommandData data) {
        asyncSend(commandId, autoRequestId.getAndIncrement(), data);
    }

    public void asyncSend(int commandId, int requestId, ICommandData data) {
        ICommandPacket sendPacket;
        try {
            sendPacket = buildPacketFromData(commandId, requestId, data);
        } catch (IOException e) {
            logger.error("序列化数据时发生错误", e);
            return;
        }
        asyncSend(sendPacket);
    }

    private ICommandPacket buildPacketFromData(int commandId, int requestId, ICommandData data) throws IOException {
        CommandPacket packet = new CommandPacket(commandId);
        packet.setRequestId(requestId);
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        DataOutputStream dataOutput = new DataOutputStream(outStream);
        data.encodeToOutput(dataOutput);
        packet.setCarryingData(outStream.toByteArray());
        return packet;
    }

    public ICommandPacket syncSend(ICommandPacket sendPacket) {
        logger.debug("设备的nkAgent连接状态：connected = " + connected);
        if (!connected) return null;
        SendTarget sendTarget = new SendTarget(sendPacket, false);
        sendQueue.offer(sendTarget);
        try {
            synchronized (sendTarget.lock) {
                sendTarget.lock.wait(SYNC_TIMEOUT * 1000);
            }
        } catch (InterruptedException e) {
            return null;
        }
        return sendTarget.responsePacket;
    }

    public void asyncSend(ICommandPacket sendPacket) {
        if (!connected) return;
        SendTarget sendTarget = new SendTarget(sendPacket, true);
        sendQueue.offer(sendTarget);
    }

    /**
     * 添加一个异步结果监听器
     *
     * @param listener 监听器
     */
    public void addAsyncListener(AsyncPacketListener listener) {
        synchronized (asyncPacketListeners) {
            asyncPacketListeners.add(listener);
        }
    }

    public void setOnDisconnectListener(Runnable onDisconnectListener) {
        this.onDisconnectListener = onDisconnectListener;
    }




    /**
     * 移除一个异步结果监听器
     *
     * @param listener 监听器
     */
    public void removeAsyncListener(AsyncPacketListener listener) {
        synchronized (asyncPacketListeners) {
            asyncPacketListeners.remove(listener);
        }
    }

    public void close() {
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                logger.error("关闭Socket时发生IO异常", e);
            }
        }
    }

    public boolean isConnected() {
        return connected;
    }

    private void onDisconnected() {
        connected = false;
        sendHandler.stop();
        receiveHandler.stop();
        asyncPacketHandler.stop();
        sendQueue.clear();
        synchronized (syncWaitResponseTargetList) {
            for (SendTarget target : syncWaitResponseTargetList) {
                synchronized (target.lock) {
                    target.lock.notifyAll();
                }
            }
            syncWaitResponseTargetList.clear();
        }
        synchronized (asyncPacketListeners) {
            asyncPacketListeners.clear();
        }
        if (onDisconnectListener != null) {
            try {
                onDisconnectListener.run();
            } catch (Exception e) {
                logger.error("执行连接断开回调时发生异常", e);
            }
        }
    }

    /**
     * 执行发送操作的对象
     * <br>
     * 内部处理了同步响应锁
     */
    public class SendTarget implements PacketHandler {
        private final Object lock = new Object();
        final ICommandPacket packet;
        final long time = System.currentTimeMillis();
        private ICommandPacket responsePacket = null;
        final boolean isAsync;

        public SendTarget(ICommandPacket packet, boolean isAsync) {
            this.packet = packet;
            this.isAsync = isAsync;
        }

        public ICommandPacket getResponsePacket() throws SyncRequestTimeoutException {
            if (isTimeout()) throw new SyncRequestTimeoutException(this);
            return responsePacket;
        }

        public boolean isTimeout() {
            return System.currentTimeMillis() - time > SYNC_TIMEOUT * 1000;
        }

        @Override
        public void handle(PacketTransfer transfer, ICommandPacket packet) {
            this.responsePacket = packet;
            synchronized (lock) {
                lock.notifyAll();
            }
        }
    }

    /**
     * 发送数据处理器
     */
    private class SendHandler implements Runnable {

        private final Thread thread;

        public SendHandler() {
            thread = new Thread(this);
            thread.setName("PacketTransfer-" + TAG + "-SendHandler");
            thread.start();
        }

        @Override
        public void run() {
            while (!thread.isInterrupted()) {
                SendTarget sendTarget;
                try {
                    sendTarget = sendQueue.take();
                } catch (InterruptedException e) {
                    break;
                }
                if (!sendTarget.isAsync) {
                    synchronized (syncWaitResponseTargetList) {
                        syncWaitResponseTargetList.add(sendTarget);
                    }
                }
                byte[] sendByteArray = sendTarget.packet.toByteArray();
                logger.debug("发送commandId:" + sendTarget.packet.commandId() + " requestId:" + sendTarget.packet.getRequestId() + " dataLength:" + (sendByteArray.length - 12) + ":" + ByteUtils.bytesToHex(sendByteArray).substring(24));
                try {
                    outputStream.write(sendByteArray);
                } catch (IOException e) {
                    logger.error("PacketTransfer-" + TAG + "发送数据时发生IO异常", e);
                    break;
                }
            }
            onDisconnected();
        }

        public void stop() {
            if (!thread.isInterrupted()) {
                thread.interrupt();
            }
        }
    }

    /**
     * 接收数据处理器
     */
    private class ReceiveHandler implements Runnable {
        private final Thread thread;

        public ReceiveHandler() {
            thread = new Thread(this);
            thread.setName("PacketTransfer-" + TAG + "-ReceiveHandler");
            thread.start();
        }

        @Override
        public void run() {
            while (!thread.isInterrupted()) {
                int commandId;
                try {
                    commandId = inputStream.readInt();
                } catch (IOException e) {
                    logger.error("PacketTransfer-" + TAG + "读取指令ID阶段发生IO异常", e);
                    break;
                }
                int requestId;
                try {
                    requestId = inputStream.readInt();
                } catch (IOException e) {
                    logger.error("PacketTransfer-" + TAG + "读取请求ID阶段发生IO异常", e);
                    break;
                }
                int dataLength;
                try {
                    dataLength = inputStream.readInt();
                } catch (IOException e) {
                    logger.error("PacketTransfer-" + TAG + "读取数据长度阶段发生IO异常", e);
                    break;
                }
                byte[] carryingData = new byte[dataLength];
                try {
                    inputStream.readFully(carryingData);
                } catch (IOException e) {
                    logger.error("PacketTransfer-" + TAG + "读取携带数据阶段发生IO异常", e);
                    break;
                }
                logger.debug("收到commandId:" + commandId + " requestId:" + requestId + " dataLength:" + dataLength + ":" + ByteUtils.bytesToHex(carryingData));

                SendTarget sendTarget = null;
                synchronized (syncWaitResponseTargetList) {
                    int foundIndex = -1;
                    for (int i = 0; i < syncWaitResponseTargetList.size(); i++) {
                        SendTarget target = syncWaitResponseTargetList.get(i);
                        if (target.packet.commandId() == commandId && requestId == target.packet.getRequestId()) {
                            sendTarget = target;
                            foundIndex = i;
                            break;
                        }
                    }
                    if (sendTarget != null) {
                        syncWaitResponseTargetList.remove(foundIndex);
                    }
                }
                if (sendTarget != null) {
                    //同步请求响应
                    ICommandPacket packet = sendTarget.packet.createResponse(carryingData);
                    sendTarget.handle(PacketTransfer.this, packet);
                } else {
                    //异步请求响应
                    ArrayList<AsyncPacketListener> matchListener = new ArrayList<>();
                    synchronized (asyncPacketListeners) {
                        for (AsyncPacketListener listener : asyncPacketListeners) {
                            if (listener.getCommandId() == commandId && listener.getRequestId() == requestId) {
                                matchListener.add(listener);
                            }
                        }
                    }
                    System.out.println("match listener:" + matchListener.size());
                    if (!matchListener.isEmpty()) {
                        CommandPacket packet = new CommandPacket(commandId);
                        packet.setRequestId(requestId);
                        packet.setCarryingData(carryingData);

                        for (AsyncPacketListener listener : matchListener) {
                            asyncPacketHandler.jobQueue.offer(new AsyncPacketHandleJob(listener, packet));
                        }
                    }

                }
            }
            onDisconnected();
        }

        public void stop() {
            if (!thread.isInterrupted()) {
                thread.interrupt();
            }
        }
    }

    /**
     * 用一个单独线程处理异步数据响应，防止一些响应时间较长且没有另开线程从而导致阻止之后的数据读取
     */
    private class AsyncPacketHandler implements Runnable {

        private final Thread thread;

        private final ArrayBlockingQueue<AsyncPacketHandleJob> jobQueue = new ArrayBlockingQueue<>(10000);

        public AsyncPacketHandler() {
            thread = new Thread(this);
            thread.setName("PacketTransfer-" + TAG + "-AsyncPacketHandler");
            thread.start();
        }

        @Override
        public void run() {
            while (!thread.isInterrupted()) {
                AsyncPacketHandleJob job;
                try {
                    job = jobQueue.take();
                } catch (InterruptedException e) {
                    break;
                }
                job.listener.tryHandle(PacketTransfer.this, job.packet);
            }
        }

        public void stop() {
            if (!thread.isInterrupted()) {
                thread.interrupt();
            }
        }
    }

    private static class AsyncPacketHandleJob {
        final AsyncPacketListener listener;
        final ICommandPacket packet;

        public AsyncPacketHandleJob(AsyncPacketListener listener, ICommandPacket packet) {
            this.listener = listener;
            this.packet = packet;
        }
    }

    public class SyncRequestTimeoutException extends Exception {
        public SyncRequestTimeoutException(SendTarget sendTarget) {
            super("PacketTransfer-" + TAG + "请求发生超时，指令ID：" + sendTarget.packet.commandId() + " 请求ID：" + sendTarget.packet.getRequestId() + " 创建时间：" + sendTarget.time);
        }
    }

}
