using System.IO;

namespace WinSCP
{
    internal class PatientFileStream : Stream
    {
        public PatientFileStream(Session session, string path, FileMode mode, FileAccess access, FileShare share)
        {
            _stream = new FileStream(path, mode, access, share);
            _session = session;
        }

        override public void Close()
        {
            if (_stream != null)
            {
                _stream.Close();
                _stream = null;
            }

            base.Close();
        }

        private const int InitialInterval = 50;

        public override int Read(byte[] array, int offset, int count)
        {
            int result;

            int interval = InitialInterval;
            do
            {
                result = _stream.Read(array, offset, count);
                if (result == 0)
                {
                    Wait(ref interval);
                }
                else
                {
                    _session.Logger.WriteLineLevel(2, "Read {0} bytes from log", result);
                }
            }
            // We always want to return something.
            // No attempt to detect real end of file is needed,
            // as we should not try to read beyond the final closing tag
            while (result == 0);

            return result;
        }

        private void Wait(ref int interval)
        {
            _session.Logger.WriteLine("Waiting for log update and dispatching events for {0}", interval);
            _session.DispatchEvents(interval);
            _session.CheckForTimeout();
            if (interval < 500)
            {
                interval *= 2;
            }
        }

        public override void Flush()
        {
            throw _session.Logger.WriteException(new System.NotImplementedException());
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw _session.Logger.WriteException(new System.NotImplementedException());
        }

        public override void SetLength(long value)
        {
            throw _session.Logger.WriteException(new System.NotImplementedException());
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw _session.Logger.WriteException(new System.NotImplementedException());
        }

        public override bool CanRead
        {
            get
            {
                bool result = _stream.CanRead;
                _session.Logger.WriteLineLevel(2, "Can read = {0}", result);
                return result;
            }
        }

        public override bool CanSeek
        {
            get
            {
                bool result = _stream.CanSeek;
                _session.Logger.WriteLineLevel(2, "Can seek = {0}", result);
                return result;
            }
        }

        public override bool CanWrite => false;

        public override long Length
        {
            get
            {
                long result = _stream.Length;

                if (result == 0)
                {
                    _session.Logger.WriteLineLevel(2, "File is empty yet, waiting", result);

                    int interval = InitialInterval;
                    do
                    {
                        result = _stream.Length;
                        if (result == 0)
                        {
                            Wait(ref interval);
                        }
                        else
                        {
                            _session.Logger.WriteLineLevel(2, "File length = {0}", result);
                        }
                    }
                    while (result == 0);
                }

                return result;
            }
        }

        public override long Position
        {
            get
            {
                long result = _stream.Length;
                _session.Logger.WriteLineLevel(2, "File position = {0}", result);
                return result;
            }

            set
            {
                _session.Logger.WriteLineLevel(2, "Setting file position to {0}", value);
                _stream.Position = value;
            }
        }

        private FileStream _stream;
        private readonly Session _session;
    }
}
