/// <summary>
        /// Reads a string up to the specified <i>text</i> in the input buffer.
        /// </summary>
        /// <remarks>
        /// The ReadTo() function will read text from the byte buffer up to a predetermined
        /// limit (1024 characters) when looking for the string <i>text</i>. If <i>text</i>
        /// is not found within this limit, data is thrown away and more data is read
        /// (effectively consuming the earlier bytes).
        /// <para>this method is provided as compatibility with the Microsoft implementation. 
        /// There are some important differences however. This method attempts to fix a minor
        /// pathological problem with the Microsoft implementation. If the string <i>text</i>
        /// is not found, the MS implementation may modify the internal state of the decoder.
        /// As a workaround, it pushes all decoded characters back into its internal byte
        /// buffer, which fixes the problem that a second call to the ReadTo() method returns
        /// the consistent results, but a call to Read(byte[], ..) may return data that was
        /// not actually transmitted by the DCE. This would happen in case that an invalid
        /// byte sequence was found, converted to a fallback character. The original byte
        /// sequence is removed and replaced with the byte equivalent of the fallback
        /// character.</para>
        /// <para>This method is rather slow, because it tries to preserve the byte buffer
        /// in case of failure. If your application works entirely by using character
        /// sequences, you may get much better performance by using Read(char[], ...) and
        /// searching for string sequences yourself, as Read(char[], ...) does a complete
        /// conversion bytes to characters in as few steps as possible.</para>
        /// </remarks>
        /// <param name="text">The text to indicate where the read operation stops.</param>
        /// <returns>The contents of the input buffer up to the specified <i>text</i>.</returns>
        public string ReadTo(string text)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (string.IsNullOrEmpty(text)) throw new ArgumentException("Parameter text shall not be null or empty", "text");

            // Decoders in .NET are designed for streams and not really for reading
            // a little bit of data. By design, they are "greedy", they consume as much
            // byte data as possible. The data that they consume is cached internally.
            // Because it's not possible to ask the decoder only to decode if there
            // is sufficient bytes, we have to keep account of how many bytes are
            // consumed for each character.

            // Hence, m_ReadIncomplete tells us if we've consumed bytes, but have not
            // yet received a converted character.

            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: BEGIN");

            bool changedtext = !text.Equals(m_ReadToString);
            if (changedtext) {
                // Last search might have had a read overflow and we're now looking for a different
                // string. So we need to reset our buffer and start from the beginning
                ReadToOverflowCheck();
            }

            int readlen = m_Read.Length - (m_ReadIncomplete ? 1 : 0);
            if (changedtext && readlen >= text.Length) {
                // Check if the text already exists
                string lbuffer = m_Read.GetString(readlen);
                int p = lbuffer.IndexOf(text, StringComparison.Ordinal);
                if (p != -1) {
                    // It does exist, so consume up to the buffered portion
                    string result = lbuffer.Substring(0, p);
                    int l = p + text.Length;
                    ReadToConsume(l);
                    return result;
                }
            }

            TimerExpiry te = new TimerExpiry(m_ReadTimeout);
            while (!ReadToMatch(text)) {
                int bytesRead;
                int c = m_SerialPort.SerialPortIo.PeekChar(m_ReadOffset, Decoder, out bytesRead);
                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                    "ReadTo: c = {0} = PeekChar(m_ReadOffset={1}, Decoder, out bytesRead={2})", c, m_ReadOffset, bytesRead);
                if (c != -1) {
                    if (m_ReadIncomplete) {
                        int lb = m_Read.Length - 1;
                        m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                            "ReadTo: m_ReadIncomplete == true; m_Read[{0}]={1}; m_ReadOffsets[{0}]={2}", lb, (char)c, m_ReadOffsets[lb]);
                        m_Read[lb] = (char)c;
                        m_ReadOffsets[lb] += bytesRead;
                        m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                            "ReadTo: m_ReadIncomplete == true; m_Read[{0}]={1}; m_ReadOffsets[{0}]={2}", lb, (char)c, m_ReadOffsets[lb]);
                    } else {
                        if (m_Read.Free == 0) {
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free==0");

                            // Discard the oldest character in preference for the newer character
                            if (m_ReadOverflow == -1) {
                                // Overflow. We capture the first byte, because we'll have to reset the decoder later
                                m_ReadOverflowChar = m_Read[0];
                                m_ReadOverflow = m_ReadOffsets[0];
                            }
                            m_Read.Consume(1);
                            m_ReadOffsets.Consume(1);
                        }
                        m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                            "ReadTo: m_ReadIncomplete == false; m_Read.Append({0}); m_ReadOffsets.Append({1})", (char)c, bytesRead);
                        m_Read.Append((char)c);
                        m_ReadOffsets.Append(bytesRead);
                    }
                    m_ReadOffset += bytesRead;
                    m_ReadIncomplete = false;
                    m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0}", m_ReadOffset);
                } else {
                    if (bytesRead > 0) {
                        if (!m_ReadIncomplete) {
                            // The decoder consumed bytes, even though we don't have a character
                            if (m_Read.Free == 0) {
                                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free == 0");
                                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_Read.Consume(1)");
                                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0} - {1}", m_ReadOffset, m_ReadOffsets[0]);

                                // Discard the oldest character in preference for the newer character
                                if (m_ReadOverflow == -1) {
                                    // Overflow. We capture the first byte, because we'll have to reset the decoder later
                                    m_ReadOverflowChar = m_Read[0];
                                    m_ReadOverflow = m_ReadOffsets[0];
                                }
                                m_Read.Consume(1);
                                m_ReadOffsets.Consume(1);

                                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0}", m_ReadOffset);
                            }
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free == {0}", m_Read.Free);
                            m_Read.Append((char)0);
                            m_ReadOffsets.Append(bytesRead);
                            m_ReadOffset += bytesRead;
                            m_ReadIncomplete = true;
                        } else {
                            int lb = m_Read.Length - 1;
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                                "ReadTo: m_ReadIncomplete == true; m_ReadOffsets[{0}]={1}", lb, m_ReadOffsets[lb]);
                            m_ReadOffsets[lb] += bytesRead;
                            m_ReadOffset += bytesRead;
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                                "ReadTo: m_ReadIncomplete == true; m_ReadOffsets[{0}]={1}", lb, m_ReadOffsets[lb]);
                        }
                    } else {
                        m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                            "ReadTo: WaitForReadEvent({0}, {1})", m_ReadOffset + 1, te.RemainingTime());
                        // Need to wait for the next byte
                        if (!m_SerialPort.SerialPortIo.WaitForReadEvent(m_ReadOffset + 1, te.RemainingTime())) {
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: TIMEOUT");
                            // Remember the string for the next timeout
                            m_ReadToString = text;
                            throw new TimeoutException();
                        }
                    }
                }
            }

            m_SerialPort.SerialPortIo.DiscardInBuffer(m_ReadOffset);
            string rbuffer = m_Read.GetString();
            rbuffer = rbuffer.Substring(0, m_Read.Length - text.Length);
            ReadToReset();
            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: RESULT = {0}", rbuffer);
            return rbuffer;
        }
        /// <summary>
        /// Synchronously reads one character from the SerialPortStream input buffer.
        /// </summary>
        /// <returns>The character that was read. -1 indicates no data was available 
        /// within the timeout.</returns>
        public int ReadChar()
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");

            // Dealing with cached characters read by the ReadTo() method
            ReadToOverflowCheck();
            if (IsReadToBuffered()) {
                // Check there is at least one valid byte. if the condition:
                // (m_ReadIncomplete == true && m_Read.Length == 1 || m_Read.Length < 1)
                // then there are no valid bytes available.
                if (!m_ReadIncomplete || m_Read.Length > 1) {
                    int value = m_Read[0];
                    ReadToConsume(1);
                    return value;
                }
            }

            ReadToReset();

            // Reading data from the byte buffer
            TimerExpiry t = new TimerExpiry(m_ReadTimeout);
            int timeout = m_ReadTimeout;
            do {
                if (!m_SerialPort.SerialPortIo.WaitForReadEvent(timeout)) return -1;
                int c = m_SerialPort.SerialPortIo.ReadChar(this.Decoder);
                if (c != -1) return c;
                timeout = t.RemainingTime();
            } while (timeout > 0);
            return -1;
        }
                /// <summary>
                /// Wait for sufficient buffer space to be available in the write buffer within a timeout.
                /// </summary>
                /// <param name="count">The number of bytes that should be available.</param>
                /// <param name="timeout">The timeout in milliseconds.</param>
                /// <returns><b>true</b> if there is at least <c>count</c> bytes available in the write
                /// buffer within <c>timeout</c> milliseconds. <b>false</b> is always returned if no
                /// buffers have been allocated or if the serial thread isn't running (without a timeout).
                /// </returns>
                public bool WaitForWriteEvent(int count, int timeout)
                {
                    if (m_Buffers == null || !IsRunning) return false;

                    int free;
                    lock (m_WriteLock) {
                        // If we can't write the entire buffer to the WRITE buffer, we should wait
                        free = m_Buffers.WriteBuffer.Free;
                        if (free < count) m_WriteBufferNotFullEvent.Reset();
                    }

                    // Wait until we can write the full amount of data to the buffer
                    TimerExpiry exp = new TimerExpiry(timeout);
                    while (free < count) {
                        if (!m_WriteBufferNotFullEvent.WaitOne(exp.RemainingTime())) {
                            return false;
                        } else {
                            lock (m_WriteLock) {
                                // If we can't write the entire buffer to the WRITE buffer, we should wait
                                free = m_Buffers.WriteBuffer.Free;
                                if (free < count) m_WriteBufferNotFullEvent.Reset();
                            }
                        }
                    }

                    return true;
                }
        /// <summary>
        /// Reads a number of characters from the SerialPortStream input buffer and writes
        /// them into an array of characters at a given offset.
        /// </summary>
        /// <remarks>
        /// This function converts the data in the local buffer to characters based on the
        /// encoding defined by the encoding property. The encoder used may buffer data between
        /// calls if characters may require more than one byte of data for its interpretation
        /// as a character.
        /// </remarks>
        /// <param name="buffer">The character array to write the input to.</param>
        /// <param name="offset">Offset into the buffer where to start putting the data.</param>
        /// <param name="count">Maximum number of bytes to read into the buffer.</param>
        /// <returns>The actual number of bytes copied into the buffer, 0 if there was a timeout.</returns>
        public int Read(char[] buffer, int offset, int count)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (buffer == null) throw new ArgumentNullException("buffer", "NULL buffer provided");
            if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Negative offset provided");
            if (count < 0) throw new ArgumentOutOfRangeException("count", "Negative count provided");
            if (buffer.Length - offset < count) throw new ArgumentException("offset and count exceed buffer boundaries");
            if (count == 0) return 0;

            // Dealing with cached characters read by the ReadTo() method
            int cached = 0;
            ReadToOverflowCheck();
            if (IsReadToBuffered()) {
                int readlen = Math.Min(count, m_Read.Length - (m_ReadIncomplete ? 1 : 0));
                if (readlen > 0) {
                    cached = m_Read.CopyTo(buffer, offset, readlen);
                    ReadToConsume(cached);

                    if (cached == count) {
                        // Read operation is complete, may/may not be data in the cache
                        return cached;
                    }
                    offset += cached;
                    count -= cached;

                    // If there are no bytes in the serial driver, we don't need to wait
                    if (m_SerialPort.SerialPortIo.BufferedBytesToRead == 0) return cached;
                }
            }

            ReadToReset();

            // Obtaining bytes from our buffered I/O thread
            TimerExpiry t = new TimerExpiry(m_ReadTimeout);
            int timeout = m_ReadTimeout;
            do {
                if (!m_SerialPort.SerialPortIo.WaitForReadEvent(timeout)) return 0;
                int c = m_SerialPort.SerialPortIo.Read(buffer, offset, count, this.Decoder);
                if (c > 0) return c;
                timeout = t.RemainingTime();
            } while (timeout > 0);
            return 0;
        }
 /// <summary>
 /// Wait for there to be at least <i>bytes</i> by a timeout.
 /// </summary>
 /// <param name="bytes">Number of bytes expected to be in the input buffer</param>
 /// <param name="timeout">Timeout in milliseconds</param>
 /// <returns><b>true</b> if there is at least <i>bytes</i> number of data in the
 /// read buffer. <b>false</b> if there is not enough data within the timeout
 /// specified.</returns>
 public bool WaitForReadEvent(int bytes, int timeout)
 {
     if (IsRunning) {
         TimerExpiry te = new TimerExpiry(timeout);
         while (true) {
             lock (m_ReadLock) {
                 if (m_Buffers.ReadBuffer.Length >= bytes) return true;
                 m_ReadBufferEvent.Reset();
             }
             if (!m_ReadBufferEvent.WaitOne(timeout)) return false;
             timeout = te.RemainingTime();
         }
     } else {
         if (m_Buffers == null) return false;
         if (m_Buffers.ReadBuffer.Length < bytes) {
             // No data in the buffer to read. No need to timeout, as we're not running
             // and therefore don't expect more data
             return false;
         }
     }
     return true;
 }