package com.yeejoin.amos.uart.rxtx;


import com.yeejoin.amos.uart.rxtx.parse.SerialDataParser;
import com.yeejoin.amos.uart.rxtx.processor.SerialByteDataProcessor;
import com.yeejoin.amos.uart.rxtx.processor.SerialDataProcessor;
import com.yeejoin.amos.uart.rxtx.reader.SerialReader;
import gnu.io.RXTXPort;
import gnu.io.SerialPort;
import gnu.io.SerialPortEventListener;
import org.springframework.util.ObjectUtils;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TooManyListenersException;

/**
 * @author han xinjian
 **/
public class SerialContext {

    public static List<SerialContext> queue = new ArrayList<SerialContext>();

    private SerialPort serialPort;

    private SerialReader serialReader;

    private SerialByteDataProcessor serialByteDataProcessor;

    private SerialPortEventListener serialPortEventListener;

    private SerialContext() {};

    public static SerialContext findSerialContextBySerialPort(String serialPort) throws Exception {
        if (ObjectUtils.isEmpty(serialPort)) {
            throw new Exception("传入参数不可以为空");
        }
        for(SerialContext context: queue) {
            System.out.println(context.getSerialPort().getName());
            if (serialPort.equals(context.getSerialPort().getName())) {
                return context;
            }
        }
        return null;
    }

    public SerialContext build() {
        return new SerialContext();
    }

    public static SerialContext build(SerialPort serialPort) {
        SerialContext serialContext =  new SerialContext();
        serialContext.setSerialPort(serialPort);
        queue.add(serialContext);
        return serialContext;
    }

    public static SerialContext build(SerialPort serialPort, SerialByteDataProcessor serialByteDataProcessor, SerialPortEventListener serialPortEventListener) {
        SerialContext serialContext =  new SerialContext();
        serialContext.setSerialPort(serialPort);
        serialContext.setSerialByteDataProcessor(serialByteDataProcessor);
        serialContext.serialPortEventListener = serialPortEventListener;
        autoSerialPortAddEventListener(serialContext, serialPort);
        queue.add(serialContext);
        return serialContext;
    }

    public static SerialContext build(SerialPort serialPort, SerialPortEventListener serialPortEventListener) {
        SerialContext serialContext =  new SerialContext();
        serialContext.setSerialPort(serialPort);
        serialContext.serialPortEventListener = serialPortEventListener;
        autoSerialPortAddEventListener(serialContext, serialPort);
        queue.add(serialContext);
        return serialContext;
    }

    public SerialPortEventListener getSerialPortEventListener() {
        return serialPortEventListener;
    }

    public void setSerialPortEventListener(SerialPortEventListener serialPortEventListener) {
        this.serialPortEventListener = serialPortEventListener;
        autoSerialPortAddEventListener(this, serialPort);
    }

    private static void autoSerialPortAddEventListener(SerialContext serialContext, SerialPort serialPort) {
        if (serialContext.serialPortEventListener != null && serialPort != null) {
            serialPort.removeEventListener();
            try {
                serialPort.addEventListener(serialContext.serialPortEventListener);
                serialPort.notifyOnDataAvailable(true);
            } catch (TooManyListenersException e) {
                e.printStackTrace();
            }
        }
    }

    public SerialReader getSerialReader() {
        return serialReader;
    }

    public SerialByteDataProcessor getSerialByteDataProcessor() {
        return serialByteDataProcessor;
    }

    public void setSerialByteDataProcessor(SerialByteDataProcessor serialByteDataProcessor) {
        this.serialByteDataProcessor = serialByteDataProcessor;
    }

    public void setSerialDataParserSet(Set<SerialDataParser> serialDataParserSet) {
        this.serialDataParserSet = serialDataParserSet;
    }

    public void setSerialDataProcessorSet(Set<SerialDataProcessor> serialDataProcessorSet) {
        this.serialDataProcessorSet = serialDataProcessorSet;
    }

    private Set<SerialDataParser> serialDataParserSet =
            Collections.synchronizedSet(new HashSet<SerialDataParser>());

    public Set<SerialDataProcessor> serialDataProcessorSet =
            Collections.synchronizedSet(new HashSet<SerialDataProcessor>());

    public Set<SerialDataProcessor> getSerialDataProcessorSet() {
        return serialDataProcessorSet;
    }

    public Set<SerialDataParser> getSerialDataParserSet() {
        return serialDataParserSet;
    }

    public void setSerialReader(SerialReader serialReader) {
        this.serialReader = serialReader;
    }

    public void setSerialPort(SerialPort serialPort) {
        this.serialPort = serialPort;
        autoSerialPortAddEventListener(this, serialPort);
    }

    public final int DEFAULT_OUT_TIME = 100;

    public byte[] sendAndRead(byte[] data) {
        return sendAndRead(data, DEFAULT_OUT_TIME);
    }

    public <T> T sendAndRead(byte[] data, SerialDataParser<T> parser) {
        byte[] bytes = sendAndRead(data);
        return parser.parse(bytes);
    }

    public byte[] sendAndRead(byte[] data, int outTime) {
        serialPort.notifyOnDataAvailable(false);
        sendData(data);
        return readData(outTime);
    }

    public <T> T sendAndRead(byte[] data, int outTime, SerialDataParser<T> parser) {
        byte[] bytes = sendAndRead(data, outTime);
        return parser.parse(bytes);
    }

    public byte[] readData() {
        return readData(DEFAULT_OUT_TIME);
    }

    public byte[] readData(int outTime) {
        serialPort.notifyOnDataAvailable(false);
        while (outTime-- > 0) {
            byte[] bytes = serialReader.readBytes();
            if (bytes != null && bytes.length > 0) {
                serialPort.notifyOnDataAvailable(true);
                return bytes;
            }
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        serialPort.notifyOnDataAvailable(true);
        return null;
    }

    public boolean sendData(byte[] data) {
        try {
            if (data.length < 4094) {
                byte[] sendbytes = new byte[data.length + 2];
                sendbytes[0] = 0x00;
                System.arraycopy(data,0, sendbytes, 1, data.length);
                sendbytes[data.length + 1] = (byte) 0xff;
                serialPort.getOutputStream().write(sendbytes);
            } else {
                int need = data.length / 1024;
                if (data.length % 1024 > 0) {
                    need++;
                }
                for (int index = 0; index < need; index++) {
                    if (index == 0) {
                        byte[] sendbytes = new byte[1025];
                        sendbytes[0] = 0x00;
                        System.arraycopy(data,0, sendbytes, 1, 1024);
                        serialPort.getOutputStream().write(sendbytes);
                    }else if (index == need -1) {
                        byte[] sendbytes = new byte[data.length % 1024 + 1];
                        System.arraycopy(data,data.length - data.length % 1024, sendbytes, 0, data.length % 1024);
                        sendbytes[data.length % 1024] = (byte) 0xfe;
                        serialPort.getOutputStream().write(sendbytes);
                    } else {
                        byte[] sendbytes = new byte[1024];
                        System.arraycopy(data,1024 * index, sendbytes, 0, 1024);
                        serialPort.getOutputStream().write(sendbytes);
                    }
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

//            os.write(data);
//            serialPort.getOutputStream().write(data);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean sendData(SerialSendDataEntity obj) {
        return sendData(obj.getBytes());
    }

    public SerialPort getSerialPort() {
        return serialPort;
    }
}
