using System;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Pool;

namespace RebootKit.Engine.Network {
    public struct NetworkBufferReader : IDisposable {
        class ReaderHandle {
            public NativeSlice<byte> Data;
            public int Position;
            public bool IsBigEndian;
        }

        static readonly IObjectPool<ReaderHandle> s_ReaderPool = new ObjectPool<ReaderHandle>(
             () => new ReaderHandle(),
             _ => { },
             handle => {
                 handle.Data = default;
                 handle.Position = 0;
                 handle.IsBigEndian = false;
             },
             _ => { },
             true,
             256
            );

        ReaderHandle m_Handle;

        public NetworkBufferReader(NativeSlice<byte> data, int position = 0) {
            Assert.IsTrue(position >= 0 && position <= data.Length,
                          "Position must be within the bounds of the data array.");

            m_Handle = s_ReaderPool.Get();
            m_Handle.Data = data;
            m_Handle.Position = position;
            m_Handle.IsBigEndian = !BitConverter.IsLittleEndian;
        }

        public void Dispose() {
            if (m_Handle != null) {
                s_ReaderPool.Release(m_Handle);
                m_Handle = null;
            }
        }

        public bool HasNext(int size) {
            return m_Handle.Position + size <= m_Handle.Data.Length;
        }

        public bool Read(out NativeArray<byte> value, int size, Allocator allocator = Allocator.Temp) {
            Assert.IsTrue(HasNext(size),
                          $"Not enough data to read the requested size. Requested: {size}, Available: {m_Handle.Data.Length - m_Handle.Position}");

            if (size <= 0) {
                value = default;
                return false;
            }
            
            value = new NativeArray<byte>(size, allocator);
            for (int i = 0; i < size; i++) {
                value[i] = m_Handle.Data[m_Handle.Position++];
            }

            return true;
        }

        public bool Read(out NativeSlice<byte> value, int size) {
            if (!HasNext(size)) {
                value = default;
                return false;
            }
            
            value = m_Handle.Data.Slice(m_Handle.Position, size);
            m_Handle.Position += size;
            return true;
        }

        public bool Read(out byte value) {
            if (!HasNext(1)) {
                value = 0;
                return false;
            }

            Assert.IsTrue(HasNext(1), "Not enough data to read a byte.");
            value = m_Handle.Data[m_Handle.Position++];
            return true;
        }

        public bool Read(out bool value) {
            if (!HasNext(1)) {
                value = false;
                return false;
            }

            value = m_Handle.Data[m_Handle.Position++] != 0;
            return true;
        }

        public bool Read(out int value) {
            value = 0;

            if (!HasNext(4)) {
                return false;
            }

            if (m_Handle.IsBigEndian) {
                value |= m_Handle.Data[m_Handle.Position++] << 24;
                value |= m_Handle.Data[m_Handle.Position++] << 16;
                value |= m_Handle.Data[m_Handle.Position++] << 8;
                value |= m_Handle.Data[m_Handle.Position++];
            } else {
                value |= m_Handle.Data[m_Handle.Position++];
                value |= m_Handle.Data[m_Handle.Position++] << 8;
                value |= m_Handle.Data[m_Handle.Position++] << 16;
                value |= m_Handle.Data[m_Handle.Position++] << 24;
            }

            return true;
        }

        public bool Read(out short value) {
            value = 0;

            if (!HasNext(2)) {
                return false;
            }

            if (m_Handle.IsBigEndian) {
                value |= (short) (m_Handle.Data[m_Handle.Position++] << 8);
                value |= (short) (m_Handle.Data[m_Handle.Position++]);
            } else {
                value |= (short) (m_Handle.Data[m_Handle.Position++]);
                value |= (short) (m_Handle.Data[m_Handle.Position++] << 8);
            }

            return true;
        }

        public bool Read(out ushort value) {
            value = 0;

            if (!HasNext(2)) {
                return false;
            }

            if (m_Handle.IsBigEndian) {
                value |= (ushort) (m_Handle.Data[m_Handle.Position++] << 8);
                value |= (ushort) (m_Handle.Data[m_Handle.Position++]);
            } else {
                value |= (ushort) (m_Handle.Data[m_Handle.Position++]);
                value |= (ushort) (m_Handle.Data[m_Handle.Position++] << 8);
            }

            return true;
        }

        public bool Read(out long value) {
            value = 0;

            if (!HasNext(8)) {
                return false;
            }

            if (m_Handle.IsBigEndian) {
                value |= (long) m_Handle.Data[m_Handle.Position++] << 56;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 48;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 40;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 32;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 24;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 16;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 8;
                value |= (long) m_Handle.Data[m_Handle.Position++];
            } else {
                value |= (long) m_Handle.Data[m_Handle.Position++];
                value |= (long) m_Handle.Data[m_Handle.Position++] << 8;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 16;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 24;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 32;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 40;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 48;
                value |= (long) m_Handle.Data[m_Handle.Position++] << 56;
            }

            return true;
        }

        public bool Read(out ulong value) {
            value = 0;

            if (!HasNext(8)) {
                return false;
            }

            if (m_Handle.IsBigEndian) {
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 56;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 48;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 40;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 32;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 24;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 16;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 8;
                value |= (ulong) m_Handle.Data[m_Handle.Position++];
            } else {
                value |= (ulong) m_Handle.Data[m_Handle.Position++];
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 8;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 16;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 24;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 32;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 40;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 48;
                value |= (ulong) m_Handle.Data[m_Handle.Position++] << 56;
            }

            return true;
        }

        public bool Read(out float value) {
            if (Read(out int intValue)) {
                value = System.BitConverter.Int32BitsToSingle(intValue);
                return true;
            }

            value = 0.0f;
            return false;
        }

        public bool Read(out Vector2 value) {
            Assert.IsTrue(HasNext(sizeof(float) * 2), "Not enough data to read a Vector2.");

            if (Read(out float x) && Read(out float y)) {
                value = new Vector2(x, y);
                return true;
            }

            value = Vector2.zero;
            return false;
        }

        public bool Read(out Vector3 value) {
            Assert.IsTrue(HasNext(sizeof(float) * 3), "Not enough data to read a Vector3.");

            if (Read(out float x) && Read(out float y) && Read(out float z)) {
                value = new Vector3(x, y, z);
                return true;
            }

            value = Vector3.zero;
            return false;
        }

        public bool Read(out Vector4 value) {
            Assert.IsTrue(HasNext(sizeof(float) * 4), "Not enough data to read a Vector4.");

            if (Read(out float x) && Read(out float y) && Read(out float z) && Read(out float w)) {
                value = new Vector4(x, y, z, w);
                return true;
            }

            value = Vector4.zero;
            return false;
        }

        public bool Read(out Quaternion value) {
            Assert.IsTrue(HasNext(sizeof(float) * 4), "Not enough data to read a Quaternion.");

            if (Read(out float x) && Read(out float y) && Read(out float z) && Read(out float w)) {
                value = new Quaternion(x, y, z, w);
                return true;
            }

            value = Quaternion.identity;
            return false;
        }

        public bool Read(out FixedString32Bytes value) {
            Assert.IsTrue(HasNext(32), "Not enough data to read a FixedString32Bytes.");

            NativeArray<byte> tempData = new NativeArray<byte>(32, Allocator.Temp);

            value = new FixedString32Bytes();
            int length = 0;

            for (int i = 0; i < 32; i++) {
                Read(out byte byteValue);

                tempData[i] = byteValue;

                if (byteValue != 0) {
                    length++;
                }
            }

            value.Length = length;
            for (int i = 0; i < length; i++) {
                value[i] = tempData[i];
            }

            tempData.Dispose();
            return true;
        }

        public bool Read(out FixedString64Bytes value) {
            Assert.IsTrue(HasNext(64), "Not enough data to read a FixedString64Bytes.");

            NativeArray<byte> tempData = new NativeArray<byte>(64, Allocator.Temp);

            value = new FixedString64Bytes();
            int length = 0;

            for (int i = 0; i < 64; i++) {
                Read(out byte byteValue);

                tempData[i] = byteValue;

                if (byteValue != 0) {
                    length++;
                }
            }

            value.Length = length;
            for (int i = 0; i < length; i++) {
                value[i] = tempData[i];
            }

            tempData.Dispose();
            return true;
        }

        public bool Read(out FixedString128Bytes value) {
            Assert.IsTrue(HasNext(128), "Not enough data to read a FixedString128Bytes.");

            NativeArray<byte> tempData = new NativeArray<byte>(128, Allocator.Temp);

            value = new FixedString128Bytes();
            int length = 0;

            for (int i = 0; i < 128; i++) {
                Read(out byte byteValue);

                tempData[i] = byteValue;

                if (byteValue != 0) {
                    length++;
                }
            }

            value.Length = length;
            for (int i = 0; i < length; i++) {
                value[i] = tempData[i];
            }

            tempData.Dispose();
            return true;
        }

        public bool Read(out FixedString512Bytes value) {
            Assert.IsTrue(HasNext(512), "Not enough data to read a FixedString512Bytes.");

            NativeArray<byte> tempData = new NativeArray<byte>(512, Allocator.Temp);

            value = new FixedString512Bytes();
            int length = 0;

            for (int i = 0; i < 512; i++) {
                Read(out byte byteValue);

                tempData[i] = byteValue;

                if (byteValue != 0) {
                    length++;
                }
            }

            value.Length = length;
            for (int i = 0; i < length; i++) {
                value[i] = tempData[i];
            }

            tempData.Dispose();
            return true;
        }

        public bool Read(out FixedString4096Bytes value) {
            Assert.IsTrue(HasNext(4096), "Not enough data to read a FixedString4096Bytes.");

            NativeArray<byte> tempData = new NativeArray<byte>(4096, Allocator.Temp);

            value = new FixedString4096Bytes();
            int length = 0;

            for (int i = 0; i < 4096; i++) {
                Read(out byte byteValue);

                tempData[i] = byteValue;

                if (byteValue != 0) {
                    length++;
                }
            }

            value.Length = length;
            for (int i = 0; i < length; i++) {
                value[i] = tempData[i];
            }

            tempData.Dispose();
            return true;
        }
    }
}