using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using StormLibSharp.Native;

#pragma warning disable CA1510 // Use ArgumentNullException throw helper
#pragma warning disable CA1512 // Use ArgumentOutOfRangeException throw helper
#pragma warning disable CA1513 // Use ObjectDisposedException throw helper
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
namespace StormLibSharp
{
    public class MpqFileStream : Stream
    {
        private MpqFileSafeHandle? _handle;
        private readonly FileAccess _accessType;
        private MpqArchive? _owner;

        internal MpqFileStream(MpqFileSafeHandle handle, FileAccess accessType, MpqArchive owner)
        {
            _handle = handle;
            _accessType = accessType;
            _owner = owner;
        }

        private void VerifyHandle()
        {
            if (_handle is null || _handle.IsInvalid || _handle.IsClosed)
                throw new ObjectDisposedException("MpqFileStream");
        }

        public override bool CanRead
        {
            get { VerifyHandle(); return true; }
        }

        public override bool CanSeek
        {
            get { VerifyHandle(); return true; }
        }

        public override bool CanWrite
        {
            get { VerifyHandle(); return _accessType != FileAccess.Read; }
        }

        public override void Flush()
        {
            VerifyHandle();

            _owner?.Flush();
        }

        public override long Length
        {
            get
            {
                VerifyHandle();

                uint high = 0;
                uint low = NativeMethods.SFileGetFileSize(_handle, ref high);

                ulong val = (high << 32) | low;
                return unchecked((long)val);
            }
        }

        public override long Position
        {
            get
            {
                VerifyHandle();

                return NativeMethods.SFileGetFilePointer(_handle);
            }
            set
            {
                Seek(value, SeekOrigin.Begin);
            }
        }

        public override unsafe int Read(byte[] buffer, int offset, int count)
        {
            if (buffer is null)
                throw new ArgumentNullException(nameof(buffer));
            if (offset > buffer.Length || (offset + count) > buffer.Length)
                throw new ArgumentException();
            if (count < 0)
                throw new ArgumentOutOfRangeException(nameof(count));

            VerifyHandle();

            bool success;
            uint read;
            fixed (byte* pb = &buffer[offset])
            {
                NativeOverlapped overlapped = default;
                success = NativeMethods.SFileReadFile(_handle, new IntPtr(pb), unchecked((uint)count), out read, ref overlapped);
            }

            if (!success)
            {
                int lastError = Win32Methods.GetLastError();
                if (lastError != 38) // EOF
                    throw new Win32Exception(lastError);
            }

            return unchecked((int)read);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            VerifyHandle();

            uint low = unchecked((uint)(offset & 0xffffffffu));
            uint high = unchecked((uint)(offset >> 32));
            return NativeMethods.SFileSetFilePointer(_handle, low, ref high, (uint)origin);
        }

        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        public override unsafe void Write(byte[] buffer, int offset, int count)
        {
            VerifyHandle();

            if (buffer is null)
                throw new ArgumentNullException(nameof(buffer));
            if (offset > buffer.Length || (offset + count) > buffer.Length)
                throw new ArgumentException();
            if (count < 0)
                throw new ArgumentOutOfRangeException(nameof(count));

            VerifyHandle();

            bool success;
            fixed (byte* pb = &buffer[offset])
            {
                success = NativeMethods.SFileWriteFile(_handle, new IntPtr(pb), unchecked((uint)count), 0u);
            }

            if (!success)
                throw new Win32Exception();
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                if (_handle is not null && !_handle.IsInvalid)
                {
                    _handle.Close();
                    _handle = null;
                }

                _owner?.RemoveOwnedFile(this);
                _owner = null;
            }
        }

        // TODO: Seems like the right place for SFileGetFileInfo, but will need to determine
        // what value add these features have except for sophisticated debugging purposes
        // (like in Ladis' MPQ Editor app).

        public int ChecksumCrc32
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public byte[] GetMd5Hash()
        {
            throw new NotImplementedException();
        }
    }
}
