package net.northking.cctp.upperComputer.deviceManager.thread.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.ByteBuffer;

import static net.northking.cctp.upperComputer.deviceManager.thread.util.IOScope.BUFFER_SIZE;

/**
 * IO读取工作<br>
 * 获取和控制单个Socket读取工作情况
 */
public class IOJob implements IIOJob, Runnable {
    private final InputStream inputStream;

    private final ThreadScope handleScope;

    private final OnReadBufferCallback<IOJob> callback;


    protected final ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);

    private boolean finish = false;

    private final Socket socket;

    private Object attachment = null;

    private final Thread readThread = new Thread(this);

    /**
     * 等待其它逻辑执行markReadDone
     */
    private final Object waitDoReadLock = new Object();


    protected IOJob(Socket socket, ThreadScope handleScope, OnReadBufferCallback<IOJob> callback) throws IOException {
        this.socket = socket;
        this.inputStream = socket.getInputStream();
        this.handleScope = handleScope;
        this.callback = callback;
        readThread.start();
    }

    protected IOJob(InputStream inputStream, ThreadScope handleScope, OnReadBufferCallback<IOJob> callback) {
        this.socket = null;
        this.inputStream = inputStream;
        this.handleScope = handleScope;
        this.callback = callback;
        readThread.start();
    }


    public ThreadScope getHandleScope() {
        return handleScope;
    }

    public OnReadBufferCallback<IOJob> getCallback() {
        return callback;
    }

    /**
     * 标记byteBuffer中的数据已经被处理完毕了，新的数据可以进入byteBuffer
     */
    private void markReadDone() {
        synchronized (waitDoReadLock) {
            waitDoReadLock.notifyAll();
        }
    }


    /**
     * 标记此读取工作已经结束<br>
     * 在稍后的时机将会被IOScope释放
     */
    public void markFinish() {
        finish = true;
        if (callback instanceof BufferToLineReader) {
            handleScope.launch((job) -> {
                ((BufferToLineReader<IOJob>) callback).writeRemainText(this);
            });
        }
        if (!readThread.isInterrupted()) {
            readThread.interrupt();
        }
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException ignored) {

            }
        }

        handleScope.ioJobFinish(this);
    }

    /**
     * 是否已经结束了此读取工作
     *
     * @return 是否已经结束了工作
     */
    public boolean isFinish() {
        return finish;
    }

    @Override
    public void write(ByteBuffer buffer) {
        throw new RuntimeException("写入请使用ThreadScope进行");
    }

    @Override
    public void run() {
        int count = 0;
        byte[] buffer = new byte[BUFFER_SIZE];
        while (!handleScope.isDeposed() && !finish && !readThread.isInterrupted()) {
            try {
                count = inputStream.read(buffer);
            } catch (IOException e) {
                handleScope.launch((job) -> {
                    callback.onRead(this, null, e);
                    markReadDone();
                });
                break;
            }
            if (count < 1) {
                System.out.println("======count:" + count);
                handleScope.launch((job) -> {
                    callback.onRead(this, null, null);
                    markReadDone();
                });
                break;
            }
            byteBuffer.clear();
            byteBuffer.put(buffer, 0, count);
            handleScope.launch((job) -> {
                callback.onRead(this, byteBuffer, null);
                markReadDone();
            });
            try {
                synchronized (waitDoReadLock) {
                    waitDoReadLock.wait();
                }
            } catch (InterruptedException e) {
                break;
            }
        }
        byteBuffer.clear();
        markFinish();
    }

    @Override
    public Object getAttachment() {
        return attachment;
    }

    @Override
    public void setAttachment(Object attachment) {
        this.attachment = attachment;
    }

    /**
     * 数据读取回调<br>
     * onRead方法将在调用的ThreadScope中运行
     */
    public interface OnReadBufferCallback<IIOJob> {
        /**
         * 单词读取操作完成
         *
         * @param buffer 读取到的数据buffer，如果为null，则读取失败，检查异常
         * @param e      读取过程发生的异常，null则为一切正常
         */
        void onRead(IIOJob job, ByteBuffer buffer, Exception e);
    }

    /**
     * 行数据读取回调<br>
     * 以文本方式读取内容，当读取到换行“\n”时，进行回调。<br>
     * 若结束读取时仍缓冲有内容但是行并未结束，也会作为单行进行回调。
     */
    public interface OnReadLineCallback<JOB> {
        /**
         * 读取到新的行
         *
         * @param job  当前job
         * @param line 行内容，不包括换行符
         * @param e    如果读取过程中发生异常，通过此获取，如果一切正常，此处为null
         */
        void onLine(JOB job, String line, Exception e);
    }

    /**
     * 将缓冲区内容按行读取，并通过回调返回<br>
     * 本类逻辑将在指定的ThreadScope执行，并不在IOScope中<br>
     * 读取的内容会去除首位不可见字符
     */
    public static class BufferToLineReader<IIOJob> implements OnReadBufferCallback<IIOJob> {

        private final ByteArrayOutputStream strOutputStream = new ByteArrayOutputStream(BUFFER_SIZE);

        private String holdString = null;

        private final OnReadLineCallback<IIOJob> callback;

        public BufferToLineReader(OnReadLineCallback<IIOJob> callback) {
            this.callback = callback;
        }


        @Override
        public void onRead(IIOJob job, ByteBuffer buffer, Exception e) {
            if (buffer != null && buffer.position() > 0) {
                strOutputStream.write(buffer.array(), 0, buffer.position());
                if (holdString == null) {
                    holdString = strOutputStream.toString();
                } else {
                    holdString += strOutputStream.toString();
                }
                strOutputStream.reset();
                int wrapPosition;
                do {
                    wrapPosition = holdString.indexOf('\n');
                    if (wrapPosition >= 0) {
                        String readLine = holdString.substring(0, wrapPosition).trim(); //需要去除头尾不可见字符，否则一些控制字符将会错误的应用到终端输出效果上（例如行消失不见了，但内容实际上又是有的）
                        holdString = holdString.substring(wrapPosition + 1);
                        callback.onLine(job, readLine, e);
                    }
                } while (wrapPosition >= 0);
            } else if (e != null) {
                callback.onLine(job, null, e);
            }
        }

        /**
         * 结束读取时，将剩余的未碰到行尾换行符的内容作为单行数据传递给回调
         */
        protected void writeRemainText(IIOJob job) {
            if (holdString != null) {
                String text = holdString.trim();
                if (text.length() > 0) {
                    callback.onLine(job, holdString, null);
                }
                holdString = null;
            }
        }
    }
}
