/*
 * Copyright (c) 2011, Kustaa Nyholm / SpareTimeLabs
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list
 * of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this
 * list of conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution.
 *
 * Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its
 * contributors may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */
package com.sparetimelabs.serial;

// FIXME move javadoc comments for input stream to SerialPort.java
import com.sparetimelabs.serial.termios.JTermios;
import com.sparetimelabs.serial.termios.JTermios.JTermiosInterface.Pollfd;
import com.sparetimelabs.serial.termios.Termios;
import com.sparetimelabs.serial.termios.TimeVal;
import com.sun.jna.Native;

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

import static com.sparetimelabs.serial.termios.JTermios.*;
import static com.sparetimelabs.serial.termios.JTermios.JTermiosLogging.lineno;
import static com.sparetimelabs.serial.termios.JTermios.JTermiosLogging.log;

/**
 * The type Pure java serial port.
 */
public class PureJavaSerialPort extends SerialPort {

    /**
     * The Use poll.
     */
    final boolean USE_POLL;
    /**
     * The Raw read mode.
     */
    final boolean RAW_READ_MODE;
    private Thread m_Thread;
    private volatile SerialPortEventListener m_EventListener;
    private volatile OutputStream m_OutputStream;
    private volatile InputStream m_InputStream;
    private volatile int m_FD = -1;
    private volatile boolean m_HaveNudgePipe = false;
    private volatile int m_PipeWrFD = 0;
    private volatile int m_PipeRdFD = 0;
    private byte[] m_NudgeData = {0};
    private volatile int m_BaudRate;
    private volatile int m_DataBits;
    private volatile int m_FlowControlMode;
    private volatile int m_Parity;
    private volatile int m_StopBits;
    private volatile Object m_ThresholdTimeoutLock = new Object();
    private volatile boolean m_TimeoutThresholdChanged = true;
    private volatile boolean m_ReceiveTimeoutEnabled;
    private volatile int m_ReceiveTimeoutValue;
    private volatile int m_ReceiveTimeoutVTIME;
    private volatile boolean m_ReceiveThresholdEnabled;
    private volatile int m_ReceiveThresholdValue;
    private volatile boolean m_PollingReadMode;
    private volatile boolean m_NotifyOnDataAvailable;
    private volatile boolean m_DataAvailableNotified;
    private volatile boolean m_NotifyOnOutputEmpty;
    private volatile boolean m_OutputEmptyNotified;
    private volatile boolean m_NotifyOnRI;
    private volatile boolean m_NotifyOnCTS;
    private volatile boolean m_NotifyOnDSR;
    private volatile boolean m_NotifyOnCD;
    private volatile boolean m_NotifyOnOverrunError;
    private volatile boolean m_NotifyOnParityError;
    private volatile boolean m_NotifyOnFramingError;
    private volatile boolean m_NotifyOnBreakInterrupt;
    private volatile boolean m_ThreadRunning;
    private volatile boolean m_ThreadStarted;
    private int[] m_ioctl = {0};
    private int m_ControlLineStates;
    // we cache termios in m_Termios because we don't rely on reading it back with tcgetattr()
    // which for Mac OS X / CRTSCTS does not work, it is also more efficient
    private Termios m_Termios = new Termios();
    private int m_MinVTIME;

    private void sendDataEvents(boolean read, boolean write) {
        if (read && m_NotifyOnDataAvailable && !m_DataAvailableNotified) {
            m_DataAvailableNotified = true;
            m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.DATA_AVAILABLE, false, true));
        }
        if (write && m_NotifyOnOutputEmpty && !m_OutputEmptyNotified) {
            m_OutputEmptyNotified = true;
            m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.OUTPUT_BUFFER_EMPTY, false, true));
        }
    }

    private synchronized void sendNonDataEvents() {
        if (ioctl(m_FD, TIOCMGET, m_ioctl) < 0) {
            return; // FIXME decide what to with errors in the background thread
        }
        int oldstates = m_ControlLineStates;
        m_ControlLineStates = m_ioctl[0];
        int newstates = m_ControlLineStates;
        int changes = oldstates ^ newstates;
        if (changes == 0) {
            return;
        }

        int line;

        if (m_NotifyOnCTS && (((line = TIOCM_CTS) & changes) != 0)) {
            m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.CTS, (oldstates & line) != 0, (newstates & line) != 0));
        }

        if (m_NotifyOnDSR && (((line = TIOCM_DSR) & changes) != 0)) {
            m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.DSR, (oldstates & line) != 0, (newstates & line) != 0));
        }

        if (m_NotifyOnRI && (((line = TIOCM_RI) & changes) != 0)) {
            m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.RI, (oldstates & line) != 0, (newstates & line) != 0));
        }

        if (m_NotifyOnCD && (((line = TIOCM_CD) & changes) != 0)) {
            m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.CD, (oldstates & line) != 0, (newstates & line) != 0));
        }
    }

    @Override
    synchronized public void addEventListener(SerialPortEventListener eventListener) throws TooManyListenersException {
        checkState();
        if (eventListener == null) {
            throw new IllegalArgumentException("eventListener cannot be null");
        }
        if (m_EventListener != null) {
            throw new TooManyListenersException();
        }
        m_EventListener = eventListener;
        if (!m_ThreadStarted) {
            m_ThreadStarted = true;
            m_Thread.start();
        }
    }

    @Override
    synchronized public int getBaudRate() {
        checkState();
        return m_BaudRate;
    }

    @Override
    synchronized public int getDataBits() {
        checkState();
        return m_DataBits;
    }

    @Override
    synchronized public int getFlowControlMode() {
        checkState();
        return m_FlowControlMode;
    }

    @Override
    synchronized public int getParity() {
        checkState();
        return m_Parity;
    }

    @Override
    synchronized public int getStopBits() {
        checkState();
        return m_StopBits;
    }

    @Override
    synchronized public boolean isCD() {
        checkState();
        return getControlLineState(TIOCM_CD);
    }

    @Override
    synchronized public boolean isCTS() {
        checkState();
        return getControlLineState(TIOCM_CTS);
    }

    @Override
    synchronized public boolean isDSR() {
        checkState();
        return getControlLineState(TIOCM_DSR);
    }

    @Override
    synchronized public boolean isDTR() {
        checkState();
        return getControlLineState(TIOCM_DTR);
    }

    @Override
    synchronized public boolean isRI() {
        checkState();
        return getControlLineState(TIOCM_RI);
    }

    @Override
    synchronized public boolean isRTS() {
        checkState();
        return getControlLineState(TIOCM_RTS);
    }

    @Override
    synchronized public void notifyOnBreakInterrupt(boolean x) {
        checkState();
        m_NotifyOnBreakInterrupt = x;
    }

    @Override
    synchronized public void notifyOnCTS(boolean x) {
        checkState();
        if (x) {
            updateControlLineState(TIOCM_CTS);
        }
        m_NotifyOnCTS = x;
        nudgePipe();
    }

    @Override
    synchronized public void notifyOnCarrierDetect(boolean x) {
        checkState();
        if (x) {
            updateControlLineState(TIOCM_CD);
        }
        m_NotifyOnCD = x;
        nudgePipe();
    }

    @Override
    synchronized public void notifyOnDSR(boolean x) {
        checkState();
        if (x) {
            updateControlLineState(TIOCM_DSR);
        }
        m_NotifyOnDSR = x;
        nudgePipe();
    }

    @Override
    synchronized public void notifyOnDataAvailable(boolean x) {
        checkState();
        m_NotifyOnDataAvailable = x;
        nudgePipe();
    }

    @Override
    synchronized public void notifyOnFramingError(boolean x) {
        checkState();
        m_NotifyOnFramingError = x;
    }

    @Override
    synchronized public void notifyOnOutputEmpty(boolean x) {
        checkState();
        m_NotifyOnOutputEmpty = x;
        nudgePipe();
    }

    @Override
    synchronized public void notifyOnOverrunError(boolean x) {
        checkState();
        m_NotifyOnOverrunError = x;
    }

    @Override
    synchronized public void notifyOnParityError(boolean x) {
        checkState();
        m_NotifyOnParityError = x;
    }

    @Override
    synchronized public void notifyOnRingIndicator(boolean x) {
        checkState();
        if (x) {
            updateControlLineState(TIOCM_RI);
        }
        m_NotifyOnRI = x;
        nudgePipe();
    }

    @Override
    synchronized public void removeEventListener() {
        checkState();
        m_EventListener = null;
    }

    @Override
    synchronized public void sendBreak(int duration) {
        checkState();
        // FIXME POSIX does not specify how duration is interpreted
        // Opengroup POSIX says:
        // If the terminal is using asynchronous serial data transmission, tcsendbreak()
        // shall cause transmission of a continuous stream of zero-valued bits for a specific duration.
        // If duration is 0, it shall cause transmission of zero-valued bits for at least 0.25 seconds,
        // and not more than 0.5 seconds. If duration is not 0, it shall send zero-valued bits for an implementation-defined period of time.
        // From the man page for Linux tcsendbreak:
        // The effect of a non-zero duration with tcsendbreak() varies.
        // SunOS specifies a break of duration*N seconds,
        // where N is at least 0.25, and not more than 0.5. Linux, AIX, DU, Tru64 send a break of duration milliseconds.
        // FreeBSD and NetBSD and HP-UX and MacOS ignore the value of duration.
        // Under Solaris and Unixware, tcsendbreak() with non-zero duration behaves like tcdrain().

        tcsendbreak(m_FD, duration);
    }

    @Override
    synchronized public void setDTR(boolean x) {
        checkState();
        setControlLineState(TIOCM_DTR, x);
    }

    @Override
    synchronized public void setRTS(boolean x) {
        checkState();
        setControlLineState(TIOCM_RTS, x);
    }

    @Override
    synchronized public void disableReceiveFraming() {
        checkState();
        // Not supported
    }

    @Override
    synchronized public void disableReceiveThreshold() {
        checkState();
        synchronized (m_ThresholdTimeoutLock) {
            m_ReceiveThresholdEnabled = false;
            thresholdOrTimeoutChanged();
        }
    }

    @Override
    synchronized public void disableReceiveTimeout() {
        checkState();
        synchronized (m_ThresholdTimeoutLock) {
            m_ReceiveTimeoutEnabled = false;
            thresholdOrTimeoutChanged();
        }
    }

    @Override
    synchronized public void enableReceiveThreshold(int value) throws UnsupportedCommOperationException {
        checkState();
        if (value < 0) {
            throw new IllegalArgumentException("threshold" + value + " < 0 ");
        }
        if (RAW_READ_MODE && value > 255) {
            throw new IllegalArgumentException("threshold" + value + " > 255 in raw read mode");
        }
        synchronized (m_ThresholdTimeoutLock) {
            m_ReceiveThresholdEnabled = true;
            m_ReceiveThresholdValue = value;
            thresholdOrTimeoutChanged();
        }
    }

    @Override
    synchronized public void enableReceiveTimeout(int value) throws UnsupportedCommOperationException {
        if (value < 0) {
            throw new IllegalArgumentException("threshold" + value + " < 0 ");
        }
        if (value > 25500) {
            throw new UnsupportedCommOperationException("threshold" + value + " > 25500 ");
        }

        checkState();
        synchronized (m_ThresholdTimeoutLock) {
            m_ReceiveTimeoutEnabled = true;
            m_ReceiveTimeoutValue = value;
            thresholdOrTimeoutChanged();
        }
    }

    @Override
    synchronized public void enableReceiveFraming(int arg0) throws UnsupportedCommOperationException {
        checkState();
        throw new UnsupportedCommOperationException("receive framing not supported/implemented");
    }

    private void thresholdOrTimeoutChanged() { // only call if you hold the lock
        m_PollingReadMode = (m_ReceiveTimeoutEnabled && m_ReceiveTimeoutValue == 0) || (m_ReceiveThresholdEnabled && m_ReceiveThresholdValue == 0);
        m_ReceiveTimeoutVTIME = (m_ReceiveTimeoutValue + 99) / 100; // precalculate this so we don't need the division in read
        m_TimeoutThresholdChanged = true;
    }

    @Override
    synchronized public int getInputBufferSize() {
        checkState();
        // Not supported
        return 0;
    }

    @Override
    synchronized public int getOutputBufferSize() {
        checkState();
        // Not supported
        return 0;
    }

    @Override
    synchronized public void setFlowControlMode(int mode) throws UnsupportedCommOperationException {
        checkState();
        synchronized (m_Termios) {
            m_Termios.c_iflag &= ~IXANY;

            if ((mode & (FLOWCONTROL_RTSCTS_IN | FLOWCONTROL_RTSCTS_OUT)) != 0) {
                m_Termios.c_cflag |= CRTSCTS;
            } else {
                m_Termios.c_cflag &= ~CRTSCTS;
            }

            if ((mode & FLOWCONTROL_XONXOFF_IN) != 0) {
                m_Termios.c_iflag |= IXOFF;
            } else {
                m_Termios.c_iflag &= ~IXOFF;
            }

            if ((mode & FLOWCONTROL_XONXOFF_OUT) != 0) {
                m_Termios.c_iflag |= IXON;
            } else {
                m_Termios.c_iflag &= ~IXON;
            }

            checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios));

            m_FlowControlMode = mode;
        }
    }

    @Override
    synchronized public void setSerialPortParams(int baudRate, int dataBits, int stopBits, int parity) throws UnsupportedCommOperationException {
        checkState();
        synchronized (m_Termios) {
            Termios prev = new Termios();// (termios);

            // save a copy in case we need to restore it
            prev.set(m_Termios);

            try {
                checkReturnCode(setspeed(m_FD, m_Termios, baudRate));

                int db;
                switch (dataBits) {
                    case SerialPort.DATABITS_5:
                        db = CS5;
                        break;
                    case SerialPort.DATABITS_6:
                        db = CS6;
                        break;
                    case SerialPort.DATABITS_7:
                        db = CS7;
                        break;
                    case SerialPort.DATABITS_8:
                        db = CS8;
                        break;
                    default:
                        throw new UnsupportedCommOperationException("dataBits = " + dataBits);
                }

                int sb;
                switch (stopBits) {
                    case SerialPort.STOPBITS_1:
                        sb = 1;
                        break;
                    case SerialPort.STOPBITS_1_5:
                        // This setting must have been copied from the Win32 API and
                        // hasn't been properly thought through. 1.5 stop bits are
                        // only valid with 5 data bits and replace the 2 stop bits
                        // in this mode. This is a feature of the 16550 and even
                        // documented on MSDN
                        // As nobody is aware of course, we silently use 1.5 and 2
                        // stop bits interchangeably (just as the hardware does)
                        // Many linux drivers follow this convention and termios
                        // can't even differ between 1.5 and 2 stop bits 
                        sb = 2;
                        break;
                    case SerialPort.STOPBITS_2:
                        sb = 2;
                        break;
                    default:
                        throw new UnsupportedCommOperationException("stopBits = " + stopBits);
                }

                int fi = m_Termios.c_iflag & ~(INPCK | ISTRIP);
                int fc = m_Termios.c_cflag & ~(PARENB | CMSPAR | PARODD);
                switch (parity) {
                    case SerialPort.PARITY_NONE:
                        break;
                    case SerialPort.PARITY_EVEN:
                        fc |= PARENB;
                        break;
                    case SerialPort.PARITY_ODD:
                        fc |= PARENB;
                        fc |= PARODD;
                        break;
                    case SerialPort.PARITY_MARK:
                        fc |= PARENB;
                        fc |= CMSPAR;
                        fc |= PARODD;
                        break;
                    case SerialPort.PARITY_SPACE:
                        fc |= PARENB;
                        fc |= CMSPAR;
                        break;
                    default:
                        throw new UnsupportedCommOperationException("parity = " + parity);
                }

                // update the hardware
                fc &= ~CSIZE; /* Mask the character size bits */

                fc |= db; /* Set data bits */

                if (sb == 2) {
                    fc |= CSTOPB;
                } else {
                    fc &= ~CSTOPB;
                }

                m_Termios.c_cflag = fc;
                m_Termios.c_iflag = fi;

                if (tcsetattr(m_FD, TCSANOW, m_Termios) != 0) {
                    throw new UnsupportedCommOperationException("tcsetattr failed");
                }

                // Even if termios(3) tells us that tcsetattr succeeds if any change
                // has been made, not necessary all of them  we cannot check them by reading back
                // and checking the result as not every driver/OS playes by the rules
                // finally everything went ok, so we can update our settings
                m_BaudRate = baudRate;
                m_Parity = parity;
                m_DataBits = dataBits;
                m_StopBits = stopBits;
            } catch (UnsupportedCommOperationException e) {
                m_Termios.set(prev);
                checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios));
                throw e;
            } catch (IllegalStateException e) {
                m_Termios.set(prev);
                checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios));
                if (e instanceof PureJavaIllegalStateException) {
                    throw e;
                } else {
                    throw new PureJavaIllegalStateException(e);
                }
            }
        }
    }

    /**
     * Gets the native file descriptor used by this port.
     * <p>
     * The file descriptor can be used in calls to JTermios functions. This
     * maybe useful in extreme cases where performance is more important than
     * convenience, for example using <code>JTermios.read(...)</code> instead of
     * <code>SerialPort.getInputStream().read(...)</code>.
     * <p>
     * Note that mixing direct JTermios read/write calls with SerialPort stream
     * read/write calls is at best fragile and likely to fail, which also
     * implies that when using JTermios directly then configuring the port,
     * especially termios.cc[VMIN] and termios.cc[VTIME] is the users
     * responsibility.
     * <p>
     * Below is a sketch of minimum necessary to perform a read using raw
     * JTermios functionality.
     * <pre>
     * {@code
     * 		// import the JTermios functionality like this
     * 		import jtermios.*;
     * 		import static jtermios.JTermios.*;
     *
     * 		SerialPort port = ...;
     *
     * 		// cast the port to PureJavaSerialPort to get access to getNativeFileDescriptor
     * 		int FD = ((PureJavaSerialPort) port).getNativeFileDescriptor();
     *
     * 		// timeout and threshold values
     * 		int messageLength = 25; // bytes
     * 		int timeout = 200; // msec
     *
     * 		// to initialize timeout and threshold first read current termios
     * 		Termios termios = new Termios();
     *
     * 		if (0 != tcgetattr(FD, termios))
     * 			errorHandling();
     *
     * 		// then set VTIME and VMIN, note VTIME in 1/10th of sec and both max 255
     * 		termios.c_cc[VTIME] = (byte) ((timeout+99) / 100);
     * 		termios.c_cc[VMIN] = (byte) messageLength;
     *
     * 		// update termios
     * 		if (0 != tcsetattr(FD, TCSANOW, termios))
     * 			errorHandling();
     *
     *      ...
     * 		// allocate read buffer
     * 		byte[] readBuffer = new byte[messageLength];
     *      ...
     *
     * 		// then perform raw read, not this may block indefinitely
     * 		int n = read(FD, readBuffer, messageLength);
     * 		if (n < 0)
     * 			errorHandling();
     * }
     * </pre>
     *
     * @return the native OS file descriptor as int
     */
    public int getNativeFileDescriptor() {
        return m_FD;
    }

    @Override
    synchronized public OutputStream getOutputStream() throws IOException {
        checkState();
        if (m_OutputStream == null) {
            m_OutputStream = new OutputStream() {
                // im_ for inner class member
                private byte[] im_Buffer = new byte[2048];

                @Override
                final public void write(int b) throws IOException {
                    checkState();
                    byte[] buf = {(byte) b};
                    write(buf, 0, 1);
                }

                @Override
                final public void write(byte[] buffer, int offset, int length) throws IOException {
                    if (buffer == null) {
                        throw new IllegalArgumentException();
                    }
                    if (offset < 0 || length < 0 || offset + length > buffer.length) {
                        throw new IndexOutOfBoundsException("buffer.lengt " + buffer.length + " offset " + offset + " length " + length);
                    }
                    checkState();
                    while (length > 0) {
                        int n = buffer.length - offset;
                        if (n > im_Buffer.length) {
                            n = im_Buffer.length;
                        }
                        if (n > length) {
                            n = length;
                        }
                        if (offset > 0) {
                            System.arraycopy(buffer, offset, im_Buffer, 0, n);
                            n = JTermios.write(m_FD, im_Buffer, n);
                        } else {
                            n = JTermios.write(m_FD, buffer, n);
                        }

                        if (n < 0) {
                            PureJavaSerialPort.this.close();
                            throw new IOException();
                        }

                        length -= n;
                        offset += n;
                    }
                    m_OutputEmptyNotified = false;
                }

                @Override
                final public void write(byte[] b) throws IOException {
                    write(b, 0, b.length);
                }

                @Override
                public void close() throws IOException {
                    super.close();
                }

                @Override
                final public void flush() throws IOException {
                    checkState();
                    if (tcdrain(m_FD) < 0) {
                        close();
                        throw new IOException();
                    }
                }
            };
        }
        return m_OutputStream;
    }

    synchronized public InputStream getInputStream() throws IOException {
        checkState();
        if (m_InputStream == null) {
            // NOTE: Windows and unixes are so different that it actually might
            // make sense to have the backend (ie JTermiosImpl) to provide
            // an InputStream that is optimal for the platform, instead of
            // trying to share of the InputStream logic here and force
            // Windows backend to conform to the the POSIX select()/
            // read()/vtim/vtime model. See the amount of code here
            // and in windows.JTermiosImpl for  select() and read().
            //
            m_InputStream = new InputStream() {
                // im_ for inner class members
                private int[] im_Available = {0};
                private byte[] im_Buffer = new byte[2048];
                // this stuff is just cached/precomputed stuff to make read() faster
                private int im_VTIME = -1;
                private int im_VMIN = -1;
                private final Pollfd[] im_ReadPollFD = new Pollfd[]{new Pollfd(), new Pollfd()};
                private byte[] im_Nudge;
                private FDSet im_ReadFDSet;
                private TimeVal im_ReadTimeVal;
                private int im_PollFDn;
                private boolean im_ReceiveTimeoutEnabled;
                private int im_ReceiveTimeoutValue;
                private boolean im_ReceiveThresholdEnabled;
                private int im_ReceiveThresholdValue;
                private boolean im_PollingReadMode;
                private int im_ReceiveTimeoutVTIME;

                { // initialized block instead of construct in anonymous class
                    im_ReadFDSet = newFDSet();
                    im_ReadTimeVal = new TimeVal();
                    im_ReadPollFD[0].fd = m_FD;
                    im_ReadPollFD[0].events = POLLIN;
                    im_ReadPollFD[1].fd = m_PipeRdFD;
                    im_ReadPollFD[1].events = POLLIN;
                    im_PollFDn = m_HaveNudgePipe ? 2 : 1;
                    im_Nudge = new byte[1];
                }

                @Override
                final public int available() throws IOException {
                    checkState();
                    if (ioctl(m_FD, FIONREAD, im_Available) < 0) {
                        PureJavaSerialPort.this.close();
                        System.out.println(Native.getLastError());
                        throw new IOException();
                    }
                    return im_Available[0];
                }

                @Override
                final public int read() throws IOException {
                    checkState();
                    byte[] buf = {0};
                    int n = read(buf, 0, 1);

                    return n > 0 ? buf[0] & 0xFF : -1;
                }

                @Override
                public void close() throws IOException {
                    super.close();
                }

                @Override
                final public int read(byte[] buffer, int offset, int length) throws IOException {
                    // reads++;
                    if (buffer == null) {
                        throw new IllegalArgumentException("buffer null");
                    }
                    if (length == 0) {
                        return 0;
                    }
                    if (offset < 0 || length < 0 || offset + length > buffer.length) {
                        throw new IndexOutOfBoundsException("buffer.length " + buffer.length + " offset " + offset + " length " + length);
                    }

                    if (RAW_READ_MODE) {
                        if (m_TimeoutThresholdChanged) { // does not need the lock if we just check the value
                            synchronized (m_ThresholdTimeoutLock) {
                                int vtime = m_ReceiveTimeoutEnabled ? m_ReceiveTimeoutVTIME : 0;
                                int vmin = m_ReceiveThresholdEnabled ? m_ReceiveThresholdValue : 1;
                                synchronized (m_Termios) {
                                    m_Termios.c_cc[VTIME] = (byte) vtime;
                                    m_Termios.c_cc[VMIN] = (byte) vmin;
                                    checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios));
                                }
                                m_TimeoutThresholdChanged = false;
                            }
                        }
                        int bytesRead;
                        if (offset > 0) {
                            if (length < im_Buffer.length) {
                                bytesRead = JTermios.read(m_FD, im_Buffer, length);
                            } else {
                                bytesRead = JTermios.read(m_FD, im_Buffer, im_Buffer.length);
                            }
                            if (bytesRead > 0) {
                                System.arraycopy(im_Buffer, 0, buffer, offset, bytesRead);
                            }
                        } else {
                            bytesRead = JTermios.read(m_FD, buffer, length);
                        }
                        m_DataAvailableNotified = false;
                        return bytesRead;

                    } // End of raw read mode code

                    if (m_FD < 0) // replaces checkState call
                    {
                        failWithIllegalStateException();
                    }

                    if (m_TimeoutThresholdChanged) { // does not need the lock if we just check the alue
                        synchronized (m_ThresholdTimeoutLock) {
                            // capture these here under guard so that we get a coherent picture of the settings
                            im_ReceiveTimeoutEnabled = m_ReceiveTimeoutEnabled;
                            im_ReceiveTimeoutValue = m_ReceiveTimeoutValue;
                            im_ReceiveThresholdEnabled = m_ReceiveThresholdEnabled;
                            im_ReceiveThresholdValue = m_ReceiveThresholdValue;
                            im_PollingReadMode = m_PollingReadMode;
                            im_ReceiveTimeoutVTIME = m_ReceiveTimeoutVTIME;
                            m_TimeoutThresholdChanged = false;
                        }
                    }

                    int bytesLeft = length;
                    int bytesReceived = 0;
                    int minBytesRequired;

                    // Note for optimal performance: message length == receive threshold == read length <= 255
                    // the best case execution path is marked with BEST below
                    while (true) {
                        // loops++;
                        int vmin;
                        int vtime;
                        if (im_PollingReadMode) {
                            minBytesRequired = 0;
                            vmin = 0;
                            vtime = 0;
                        } else {
                            if (im_ReceiveThresholdEnabled) {
                                minBytesRequired = im_ReceiveThresholdValue; // BEST
                            } else {
                                minBytesRequired = 1;
                            }
                            if (minBytesRequired > bytesLeft) // in BEST case 'if' not taken
                            {
                                minBytesRequired = bytesLeft;
                            }
                            if (minBytesRequired <= 255) {
                                vmin = minBytesRequired; // BEST case
                            } else {
                                vmin = 255;
                            }

                            // FIXME someone might change m_ReceiveTimeoutEnabled
                            if (im_ReceiveTimeoutEnabled) {
                                vtime = im_ReceiveTimeoutVTIME; // BEST case
                            } else {
                                vtime = 0;
                            }
                        }
                        if (vmin != im_VMIN || vtime != im_VTIME) { // in BEST case 'if' not taken more than once for given InputStream instance
                            // ioctls++;
                            im_VMIN = vmin;
                            im_VTIME = vtime;
                            // This needs to be guarded with m_Termios so that these thing don't change on us
                            synchronized (m_Termios) {
                                m_Termios.c_cc[VTIME] = (byte) im_VTIME;
                                m_Termios.c_cc[VMIN] = (byte) im_VMIN;
                                checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios));
                            }
                        }

                        // Now wait for data to be available, except in raw read mode
                        // and polling read modes. Following looks a bit longish
                        // but  there is actually not that much code to be executed
                        boolean dataAvailable = false;
                        boolean timedout = false;
                        if (!im_PollingReadMode) {
                            int n;
                            // long T0 = System.nanoTime();
                            // do a select()/poll(), just in case this read was
                            // called when no data is available
                            // so that we will not hang for ever in a read
                            int timeoutValue = im_ReceiveTimeoutEnabled ? im_ReceiveTimeoutValue : Integer.MAX_VALUE;
                            if (USE_POLL) { // BEST case in Linux but not on
                                // Windows or Mac OS X
                                n = poll(im_ReadPollFD, im_PollFDn, timeoutValue);
                                if (n < 0 || m_FD < 0) // the port closed while we were blocking in poll
                                {
                                    throw new IOException();
                                }

                                if ((im_ReadPollFD[1].revents & POLLIN) != 0) {
                                    JTermios.read(m_PipeRdFD, im_Nudge, 1);
                                }
                                int re = im_ReadPollFD[0].revents;
                                if ((re & POLLNVAL_OUT) != 0) {
                                    throw new IOException();
                                }
                                dataAvailable = (re & POLLIN) != 0;

                            } else { // this is a bit slower but then again it is unlikely
                                // this gets executed in a low horsepower system
                                FD_ZERO(im_ReadFDSet);
                                FD_SET(m_FD, im_ReadFDSet);
                                int maxFD = m_FD;
                                if (m_HaveNudgePipe) {
                                    FD_SET(m_PipeRdFD, im_ReadFDSet);
                                    if (m_PipeRdFD > maxFD) {
                                        maxFD = m_PipeRdFD;
                                    }
                                }
                                if (timeoutValue >= 1000) {
                                    int t = timeoutValue / 1000;
                                    im_ReadTimeVal.tv_sec = t;
                                    im_ReadTimeVal.tv_usec = (timeoutValue - t * 1000) * 1000;
                                } else {
                                    im_ReadTimeVal.tv_sec = 0;
                                    im_ReadTimeVal.tv_usec = timeoutValue * 1000;
                                }
                                n = select(maxFD + 1, im_ReadFDSet, null, null, im_ReadTimeVal);
                                if (n < 0) {
                                    throw new IOException();
                                }
                                if (m_FD < 0) // the port closed while we were
                                // blocking in select
                                {
                                    throw new IOException();
                                }
                                dataAvailable = FD_ISSET(m_FD, im_ReadFDSet);
                            }
                            if (n == 0 && m_ReceiveTimeoutEnabled) {
                                timedout = true;
                            }
                        }

                        if (timedout) {
                            break;
                        }

                        // At this point data is either available or we take our
                        // chances in raw mode or this polling read which can't block
                        int bytesRead = 0;
                        if (dataAvailable || im_PollingReadMode) {
                            if (offset > 0) {
                                if (bytesLeft < im_Buffer.length) {
                                    bytesRead = JTermios.read(m_FD, im_Buffer, bytesLeft);
                                } else {
                                    bytesRead = JTermios.read(m_FD, im_Buffer, im_Buffer.length);
                                }
                                if (bytesRead > 0) {
                                    System.arraycopy(im_Buffer, 0, buffer, offset, bytesRead);
                                }
                            } else // this the BEST case execution path
                            {
                                bytesRead = JTermios.read(m_FD, buffer, bytesLeft);
                            }
                            // readtime += System.nanoTime() - T0;
                            if (bytesRead == 0) {
                                timedout = true;
                            }
                        }

                        // Now we have read data and try to return as quickly as
                        // possibly or we have timed out.
                        if (bytesRead < 0) // an error occured
                        {
                            throw new IOException();
                        }

                        bytesReceived += bytesRead;

                        if (bytesReceived >= minBytesRequired) // BEST case this if is taken and we  exit
                        {
                            break; // we have read the minimum required and will return that
                        }
                        if (timedout) {
                            break;
                        }

                        // Ok, looks like we are in for an other loop, so update
                        // the offset
                        // and loop for some more
                        offset += bytesRead;
                        bytesLeft -= bytesRead;
                    }

                    m_DataAvailableNotified = false;
                    return bytesReceived;
                }

            };
        }
        return m_InputStream;
    }

    @Override
    synchronized public int getReceiveFramingByte() {
        checkState();
        // Not supported
        return 0;
    }

    @Override
    synchronized public int getReceiveThreshold() {
        checkState();
        return m_ReceiveThresholdValue;
    }

    @Override
    synchronized public int getReceiveTimeout() {
        checkState();
        return m_ReceiveTimeoutValue;
    }

    @Override
    synchronized public boolean isReceiveFramingEnabled() {
        checkState();
        // Not supported
        return false;
    }

    @Override
    synchronized public boolean isReceiveThresholdEnabled() {
        checkState();
        return m_ReceiveThresholdEnabled;
    }

    @Override
    synchronized public boolean isReceiveTimeoutEnabled() {
        checkState();
        return m_ReceiveTimeoutEnabled;
    }

    @Override
    synchronized public void setInputBufferSize(int arg0) {
        checkState();
        // Not supported
    }

    @Override
    synchronized public void setOutputBufferSize(int arg0) {
        checkState();
        // Not supported
    }

    private void nudgePipe() {
        if (m_HaveNudgePipe) {
            write(m_PipeWrFD, m_NudgeData, 1);
        }
    }

    @Override
    synchronized public void close() {
        int fd = m_FD;
        if (fd != -1) {
            m_FD = -1;
            try {
                if (m_InputStream != null) {
                    m_InputStream.close();
                }
            } catch (IOException e) {
                log = log && log(1, "m_InputStream.close threw an IOException %s\n", e.getMessage());
            } finally {
                m_InputStream = null;
            }
            try {
                if (m_OutputStream != null) {
                    m_OutputStream.close();
                }
            } catch (IOException e) {
                log = log && log(1, "m_OutputStream.close threw an IOException %s\n", e.getMessage());
            } finally {
                m_OutputStream = null;
            }
            nudgePipe();
            int flags = fcntl(fd, F_GETFL, 0);
            flags |= O_NONBLOCK;
            int fcres = fcntl(fd, F_SETFL, flags);
            if (fcres != 0) // not much we can do if this fails, so just log it
            {
                log = log && log(1, "fcntl(%d,%d,%d) returned %d\n", fd, F_SETFL, flags, fcres);
            }

            if (m_Thread != null) {
                m_Thread.interrupt();
            }
            int err = JTermios.close(fd);
            if (err < 0) {
                log = log && log(1, "JTermios.close returned %d, errno %d\n", err, errno());
            }

            if (m_HaveNudgePipe) {
                err = JTermios.close(m_PipeRdFD);
                if (err < 0) {
                    log = log && log(1, "JTermios.close returned %d, errno %d\n", err, errno());
                }
                err = JTermios.close(m_PipeWrFD);
                if (err < 0) {
                    log = log && log(1, "JTermios.close returned %d, errno %d\n", err, errno());
                }
            }
            long t0 = System.currentTimeMillis();
            while (m_ThreadRunning) {
                try {
                    Thread.sleep(5);
                    if (System.currentTimeMillis() - t0 > 2000) {
                        break;
                    }
                } catch (InterruptedException e) {
                    break;
                }
            }
            super.close();
        }
    }

    /**
     * Instantiates a new Pure java serial port.
     *
     * @param name    the name
     * @param timeout the timeout
     * @throws PortInUseException the port in use exception
     */
/* package */
    PureJavaSerialPort(String name, int timeout) throws PortInUseException {
        super();

        boolean usepoll = false;
        if (JTermios.canPoll()) {
            String key1 = "purejavacomm.use_poll";
            String key2 = "purejavacomm.usepoll";
            if (System.getProperty(key1) != null) {
                usepoll = Boolean.getBoolean(key1);
                log = log && log(1, "use of '%s' is deprecated, use '%s' instead\n", key1, key2);
            } else if (System.getProperty(key2) != null) {
                usepoll = Boolean.getBoolean(key2);
            } else {
                usepoll = true;
            }
        }
        USE_POLL = usepoll;

        RAW_READ_MODE = Boolean.getBoolean("purejavacomm.rawreadmode");

        this.name = name;

        int tries = (timeout + 5) / 10;
        while ((m_FD = open(name, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
            if (tries-- < 0) {
                throw new PortInUseException("Unknown Application");
            }
        }

        m_MinVTIME = Integer.getInteger("purejavacomm.minvtime", 100);
        int flags = fcntl(m_FD, F_GETFL, 0);
        flags &= ~O_NONBLOCK;
        checkReturnCode(fcntl(m_FD, F_SETFL, flags));

        m_BaudRate = 9600;
        m_DataBits = SerialPort.DATABITS_8;
        m_FlowControlMode = SerialPort.FLOWCONTROL_NONE;
        m_Parity = SerialPort.PARITY_NONE;
        m_StopBits = SerialPort.STOPBITS_1;

        checkReturnCode(tcgetattr(m_FD, m_Termios));

        cfmakeraw(m_FD, m_Termios);

        m_Termios.c_cflag |= CLOCAL | CREAD;
        m_Termios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
        m_Termios.c_oflag &= ~OPOST;

        m_Termios.c_cc[VSTART] = (byte) DC1;
        m_Termios.c_cc[VSTOP] = (byte) DC3;
        m_Termios.c_cc[VMIN] = 0;
        m_Termios.c_cc[VTIME] = 0;
        checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios));

        try {
            setSerialPortParams(m_BaudRate, m_DataBits, m_StopBits, m_Parity);
        } catch (UnsupportedCommOperationException e) {
            // This really should not happen
            e.printStackTrace();
        }

        try {
            setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
        } catch (UnsupportedCommOperationException e) {
            // This really should not happen
            e.printStackTrace();
        }

        int res = ioctl(m_FD, TIOCMGET, m_ioctl);
        if (res == 0) {
            m_ControlLineStates = m_ioctl[0];
        } else {
            log = log && log(1, "ioctl(TIOCMGET) returned %d, errno %d\n", res, errno());
        }

        String nudgekey = "purejavacomm.usenudgepipe";
        if (System.getProperty(nudgekey) == null || Boolean.getBoolean(nudgekey)) {
            int[] pipes = new int[2];
            if (pipe(pipes) == 0) {
                m_HaveNudgePipe = true;
                m_PipeRdFD = pipes[0];
                m_PipeWrFD = pipes[1];
                checkReturnCode(fcntl(m_PipeRdFD, F_SETFL, fcntl(m_PipeRdFD, F_GETFL, 0) | O_NONBLOCK));
            }
        }

        Runnable runnable = new Runnable() {
            public void run() {
                try {
                    m_ThreadRunning = true;
                    // see: http://daniel.haxx.se/docs/poll-vs-select.html
                    final int TIMEOUT = Integer.getInteger("purejavacomm.pollperiod", 10);

                    TimeVal timeout = null;
                    FDSet rset = null;
                    FDSet wset = null;
                    Pollfd[] pollfd = null;
                    byte[] nudge = null;

                    if (USE_POLL) {
                        pollfd = new Pollfd[]{new Pollfd(), new Pollfd()};
                        nudge = new byte[1];
                        pollfd[0].fd = m_FD;
                        pollfd[1].fd = m_PipeRdFD;
                    } else {
                        rset = newFDSet();
                        wset = newFDSet();
                        timeout = new TimeVal();
                        int t = TIMEOUT * 1000;
                        timeout.tv_sec = t / 1000000;
                        timeout.tv_usec = t - timeout.tv_sec * 1000000;
                    }

                    while (m_FD >= 0) {
                        boolean read = (m_NotifyOnDataAvailable && !m_DataAvailableNotified);
                        boolean write = (m_NotifyOnOutputEmpty && !m_OutputEmptyNotified);
                        int n = 0;

                        boolean pollCtrlLines = m_NotifyOnCTS || m_NotifyOnDSR || m_NotifyOnRI || m_NotifyOnCD;

                        if (read || write || (!pollCtrlLines && m_HaveNudgePipe)) {
                            if (USE_POLL) {
                                short e = 0;
                                if (read) {
                                    e |= POLLIN;
                                }
                                if (write) {
                                    e |= POLLOUT;
                                }
                                pollfd[0].events = e;
                                pollfd[1].events = POLLIN;
                                if (m_HaveNudgePipe) {
                                    n = poll(pollfd, 2, -1);
                                } else {
                                    n = poll(pollfd, 1, TIMEOUT);
                                }

                                int re = pollfd[1].revents;

                                if ((re & POLLNVAL) != 0) {
                                    log = log && log(1, "poll() returned POLLNVAL, errno %d\n", errno());
                                    break;
                                }

                                if ((re & POLLIN) != 0) {
                                    read(m_PipeRdFD, nudge, 1);
                                }

                                re = pollfd[0].revents;
                                if ((re & POLLNVAL) != 0) {
                                    log = log && log(1, "poll() returned POLLNVAL, errno %d\n", errno());
                                    break;
                                }
                                read = read && (re & POLLIN) != 0;
                                write = write && (re & POLLOUT) != 0;
                            } else {
                                FD_ZERO(rset);
                                FD_ZERO(wset);
                                if (read) {
                                    FD_SET(m_FD, rset);
                                }
                                if (write) {
                                    FD_SET(m_FD, wset);
                                }
                                if (m_HaveNudgePipe) {
                                    FD_SET(m_PipeRdFD, rset);
                                }
                                n = select(m_FD + 1, rset, wset, null, m_HaveNudgePipe ? null : timeout);
                                read = read && FD_ISSET(m_FD, rset);
                                write = write && FD_ISSET(m_FD, wset);
                            }

                            if (m_FD < 0) {
                                break;
                            }
                            if (n < 0) {
                                log = log && log(1, "select() or poll() returned %d, errno %d\n", n, errno());
                                close();
                                break;
                            }
                        } else {
                            Thread.sleep(TIMEOUT);
                        }

                        if (m_EventListener != null) {
                            if (read || write) {
                                sendDataEvents(read, write);
                            }
                            if (pollCtrlLines) {
                                sendNonDataEvents();
                            }
                        }
                    }
                } catch (InterruptedException ie) {
                } finally {
                    m_ThreadRunning = false;
                }
            }
        };
        m_Thread = new Thread(runnable, getName());
        m_Thread.setDaemon(true);
    }

    synchronized private void updateControlLineState(int line) {
        checkState();

        if (ioctl(m_FD, TIOCMGET, m_ioctl) == -1) {
            throw new PureJavaIllegalStateException("ioctl(m_FD, TIOCMGET, m_ioctl) == -1");
        }

        m_ControlLineStates = (m_ioctl[0] & line) + (m_ControlLineStates & ~line);
    }

    synchronized private boolean getControlLineState(int line) {
        checkState();
        if (ioctl(m_FD, TIOCMGET, m_ioctl) == -1) {
            throw new PureJavaIllegalStateException("ioctl(m_FD, TIOCMGET, m_ioctl) == -1");
        }
        return (m_ioctl[0] & line) != 0;
    }

    synchronized private void setControlLineState(int line, boolean state) {
        checkState();
        if (ioctl(m_FD, TIOCMGET, m_ioctl) == -1) {
            throw new PureJavaIllegalStateException("ioctl(m_FD, TIOCMGET, m_ioctl) == -1");
        }

        if (state) {
            m_ioctl[0] |= line;
        } else {
            m_ioctl[0] &= ~line;
        }
        if (ioctl(m_FD, TIOCMSET, m_ioctl) == -1) {
            throw new PureJavaIllegalStateException("ioctl(m_FD, TIOCMSET, m_ioctl) == -1");
        }
    }

    private void failWithIllegalStateException() {
        throw new PureJavaIllegalStateException("File descriptor is " + m_FD + " < 0, maybe closed by previous error condition");
    }

    private void checkState() {
        if (m_FD < 0) {
            failWithIllegalStateException();
        }
    }

    private void checkReturnCode(int code) {
        if (code != 0) {
            String msg = String.format("JTermios call returned %d at %s", code, lineno(1));
            log = log && log(1, "%s\n", msg);
            try {
                close();
            } catch (Exception e) {
                StackTraceElement st = e.getStackTrace()[0];
                String msg2 = String.format("close threw %s at class %s line% d", e.getClass().getName(), st.getClassName(), st.getLineNumber());
                log = log && log(1, "%s\n", msg2);
            }
            throw new PureJavaIllegalStateException(msg);
        }
    }

    /**
     * This is not part of the PureJavaComm API, this is purely for testing, do
     * not depend on this
     *
     * @return the boolean
     */
    public boolean isInternalThreadRunning() {
        return m_ThreadRunning;
    }

}
