/**
 * Copyright (c) 2016-2018 by the respective copyright holders.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package de.csdev.ebus.core.connection;

import java.io.IOException;
import java.util.TooManyListenersException;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/**
 * @author Christian Sowada - Initial contribution
 *
 */
public class EBusSerialNRJavaSerialConnection extends AbstractEBusConnection implements SerialPortEventListener {

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

    /** The serial object */
    private SerialPort serialPort;

    /** The serial port name */
    private String port;

    public EBusSerialNRJavaSerialConnection(String port) {
        this.port = port;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.csdev.ebus.core.connection.IEBusConnection#open()
     */
    @Override
    public boolean open() throws IOException {
        try {

            final CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(port);

            if (portIdentifier != null) {

                serialPort = portIdentifier.open("de.csdev.ebus", 2000);
                serialPort.setSerialPortParams(2400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
                        SerialPort.PARITY_NONE);

                // set timeout 10 sec.
                serialPort.disableReceiveThreshold();
                serialPort.enableReceiveTimeout(10000);

                // use event to let readByte wait until data is available, optimize cpu usage
                serialPort.addEventListener(this);
                serialPort.notifyOnDataAvailable(true);

                outputStream = serialPort.getOutputStream();
                inputStream = serialPort.getInputStream();

                return true;
            }

        } catch (NoSuchPortException e) {
            logger.error("Unable to connect to serial port {}", port);

        } catch (PortInUseException e) {
            logger.error("Serial port {} is already in use", port);

        } catch (UnsupportedCommOperationException e) {
            logger.error(e.toString(), e);

        } catch (TooManyListenersException e) {
            logger.error("Too many listeners error!", e);
        }

        serialPort = null;
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see gnu.io.SerialPortEventListener#serialEvent(gnu.io.SerialPortEvent)
     */
    @Override
    public void serialEvent(SerialPortEvent event) {
        if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
            synchronized (inputStream) {
                inputStream.notifyAll();
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.csdev.ebus.core.connection.AbstractEBusConnection#close()
     */
    @Override
    public boolean close() throws IOException {
        if (serialPort == null) {
            return true;
        }

        // run the serial.close in a new not-interrupted thread to
        // prevent an IllegalMonitorStateException error
        Thread shutdownThread = new Thread(new Runnable() {
            @Override
            public void run() {

                IOUtils.closeQuietly(inputStream);
                IOUtils.closeQuietly(outputStream);

                if (serialPort != null) {
                    serialPort.close();
                    serialPort = null;
                }
            }
        }, "eBUS serial shutdown thread");

        shutdownThread.start();

        try {
            // wait for shutdown
            shutdownThread.join(2000);
        } catch (InterruptedException e) {
        }

        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.csdev.ebus.core.connection.AbstractEBusConnection#readByte(boolean)
     */
    @Override
    public int readByte(boolean lowLatency) throws IOException {
        if (lowLatency) {
            return inputStream.read();
        } else {
            if (inputStream.available() > 0) {
                return inputStream.read();

            } else {
                synchronized (inputStream) {
                    try {
                        inputStream.wait(3000);
                        return inputStream.read();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    return -1;
                }
            }
        }
    }
}

@@ -915,45 +915,10 @@
 ion 
-implements SerialPortEventListener 
 %7B%0A
+
 %0A   
@@ -1262,33 +1262,32 @@
 -Javadoc)%0A     *
- 
 %0A     * @see de.
@@ -2076,14 +2076,437 @@
 ner(
-this);
+new SerialPortEventListener() %7B%0A                    @Override%0A                    public void serialEvent(SerialPortEvent event) %7B%0A                        if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) %7B%0A                            synchronized (inputStream) %7B%0A                                inputStream.notifyAll();%0A                            %7D%0A                        %7D%0A                    %7D%0A                %7D);%0A
 %0A   
@@ -3260,381 +3260,8 @@
   *%0A
-     * @see gnu.io.SerialPortEventListener#serialEvent(gnu.io.SerialPortEvent)%0A     */%0A    @Override%0A    public void serialEvent(SerialPortEvent event) %7B%0A        if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) %7B%0A            synchronized (inputStream) %7B%0A                inputStream.notifyAll();%0A            %7D%0A        %7D%0A    %7D%0A%0A    /*%0A     * (non-Javadoc)%0A     * %0A
     
@@ -4238,23 +4238,22 @@
 avadoc)%0A
+
      *
- 
 %0A     * 

Fix "indirectly referenced" issue on extern bundles