#if MYSQL_6_10

// Copyright © 2009, 2016 Oracle and/or its affiliates. All rights reserved.
//
// MySQL Connector/NET is licensed under the terms of the GPLv2
// <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most 
// MySQL Connectors. There are special exceptions to the terms and 
// conditions of the GPLv2 as it is applied to this software, see the 
// FLOSS License Exception
// <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
//
// This program is free software; you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published 
// by the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
//
// You should have received a copy of the GNU General Public License along 
// with this program; if not, write to the Free Software Foundation, Inc., 
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

using System;
using System.IO;
using Externals.MySql.Data.Common;

namespace Externals.MySql.Data.MySqlClient
{
    /// <summary>
    /// Stream that supports timeout of IO operations.
    /// This class is used is used to support timeouts for SQL command, where a 
    /// typical operation involves several network reads/writes. 
    /// Timeout here is defined as the accumulated duration of all IO operations.
    /// </summary>

    internal class TimedStream : Stream
    {
        readonly Stream _baseStream;

        int _timeout;
        int _lastReadTimeout;
        int _lastWriteTimeout;
        readonly LowResolutionStopwatch _stopwatch;

        internal bool IsClosed { get; private set; }

        enum IOKind
        {
            Read,
            Write
        };

        /// <summary>
        /// Construct a TimedStream
        /// </summary>
        /// <param name="baseStream"> Undelying stream</param>
        public TimedStream(Stream baseStream)
        {
            this._baseStream = baseStream;
            _timeout = baseStream.CanTimeout ? baseStream.ReadTimeout : System.Threading.Timeout.Infinite;
            IsClosed = false;
            _stopwatch = new LowResolutionStopwatch();
        }


        /// <summary>
        /// Figure out whether it is necessary to reset timeout on stream.
        /// We track the current value of timeout and try to avoid
        /// changing it too often, because setting Read/WriteTimeout property
        /// on network stream maybe a slow operation that involves a system call 
        /// (setsockopt). Therefore, we allow a small difference, and do not 
        /// reset timeout if current value is slightly greater than the requested
        /// one (within 0.1 second).
        /// </summary>

        private bool ShouldResetStreamTimeout(int currentValue, int newValue)
        {
            if (!_baseStream.CanTimeout) return false;
            if (newValue == System.Threading.Timeout.Infinite
                && currentValue != newValue)
                return true;
            if (newValue > currentValue)
                return true;
            return currentValue >= newValue + 100;
        }
        private void StartTimer(IOKind op)
        {

            int streamTimeout;

            if (_timeout == System.Threading.Timeout.Infinite)
                streamTimeout = System.Threading.Timeout.Infinite;
            else
                streamTimeout = _timeout - (int)_stopwatch.ElapsedMilliseconds;

            if (op == IOKind.Read)
            {
                if (ShouldResetStreamTimeout(_lastReadTimeout, streamTimeout))
                {
                    _baseStream.ReadTimeout = streamTimeout;
                    _lastReadTimeout = streamTimeout;
                }
            }
            else
            {
                if (ShouldResetStreamTimeout(_lastWriteTimeout, streamTimeout))
                {
                    _baseStream.WriteTimeout = streamTimeout;
                    _lastWriteTimeout = streamTimeout;
                }
            }

            if (_timeout == System.Threading.Timeout.Infinite)
                return;

            _stopwatch.Start();
        }
        private void StopTimer()
        {
            if (_timeout == System.Threading.Timeout.Infinite)
                return;

            _stopwatch.Stop();

            // Normally, a timeout exception would be thrown  by stream itself, 
            // since we set the read/write timeout  for the stream.  However 
            // there is a gap between  end of IO operation and stopping the 
            // stop watch,  and it makes it possible for timeout to exceed 
            // even after IO completed successfully.
            if (_stopwatch.ElapsedMilliseconds > _timeout)
            {
                ResetTimeout(System.Threading.Timeout.Infinite);
                throw new TimeoutException("Timeout in IO operation");
            }
        }
        public override bool CanRead => _baseStream.CanRead;

        public override bool CanSeek => _baseStream.CanSeek;

        public override bool CanWrite => _baseStream.CanWrite;

        public override void Flush()
        {
            try
            {
                StartTimer(IOKind.Write);
                _baseStream.Flush();
                StopTimer();
            }
            catch (Exception e)
            {
                HandleException(e);
                throw;
            }
        }

        public override long Length => _baseStream.Length;

        public override long Position
        {
            get
            {
                return _baseStream.Position;
            }
            set
            {
                _baseStream.Position = value;
            }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            try
            {
                StartTimer(IOKind.Read);
                int retval = _baseStream.Read(buffer, offset, count);
                StopTimer();
                return retval;
            }
            catch (Exception e)
            {
                HandleException(e);
                throw;
            }
        }

        public override int ReadByte()
        {
            try
            {
                StartTimer(IOKind.Read);
                int retval = _baseStream.ReadByte();
                StopTimer();
                return retval;
            }
            catch (Exception e)
            {
                HandleException(e);
                throw;
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _baseStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _baseStream.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            try
            {
                StartTimer(IOKind.Write);
                _baseStream.Write(buffer, offset, count);
                StopTimer();
            }
            catch (Exception e)
            {
                HandleException(e);
                throw;
            }
        }

        public override bool CanTimeout => _baseStream.CanTimeout;

        public override int ReadTimeout
        {
            get { return _baseStream.ReadTimeout; }
            set { _baseStream.ReadTimeout = value; }
        }
        public override int WriteTimeout
        {
            get { return _baseStream.WriteTimeout; }
            set { _baseStream.WriteTimeout = value; }
        }

#if NETSTANDARD1_3
    public void Close()
#else
        public override void Close()
#endif
        {
            if (IsClosed)
                return;
            IsClosed = true;
#if !NETSTANDARD1_3
            _baseStream.Close();
#endif
            _baseStream.Dispose();
        }

        public void ResetTimeout(int newTimeout)
        {
            if (newTimeout == System.Threading.Timeout.Infinite || newTimeout == 0)
                _timeout = System.Threading.Timeout.Infinite;
            else
                _timeout = newTimeout;
            _stopwatch.Reset();
        }


        /// <summary>
        /// Common handler for IO exceptions.
        /// Resets timeout to infinity if timeout exception is 
        /// detected and stops the times.
        /// </summary>
        /// <param name="e">original exception</param>
        void HandleException(Exception e)
        {
            _stopwatch.Stop();
            ResetTimeout(-1);
        }
    }
}

#endif