using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;

namespace skyscraper5.Skyscraper.IO
{
    public static class StreamExtensions
    {
        [DebuggerStepThrough]
        public static ushort ReadUInt16BE(this Stream stream)
        {
			byte[] buffer = new byte[2];
            if (stream.Read(buffer, 0, 2) != 2)
                throw new EndOfStreamException();
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer, 0, 2);
            return BitConverter.ToUInt16(buffer, 0);
        }

		public static short ReadInt16LE(this Stream stream)
		{
			byte[] buffer = new byte[2];
			if (stream.Read(buffer, 0, 2) != 2)
				throw new EndOfStreamException();
			if (!BitConverter.IsLittleEndian)
				Array.Reverse(buffer, 0, 2);
			return BitConverter.ToInt16(buffer, 0);
		}

		public static ushort ReadUInt16LE(this Stream stream)
        {
			byte[] buffer = new byte[2];
			if (stream.Read(buffer, 0, 2) != 2)
                throw new EndOfStreamException();
            if (!BitConverter.IsLittleEndian)
                Array.Reverse(buffer, 0, 2);
            return BitConverter.ToUInt16(buffer, 0);
        }

        [DebuggerStepThrough]
        public static uint ReadUInt32BE(this Stream stream)
        {
			byte[] buffer = new byte[4];
            if (stream.Read(buffer, 0, 4) != 4)
                throw new EndOfStreamException();
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer, 0, 4);
            return BitConverter.ToUInt32(buffer, 0);
        }

		[DebuggerStepThrough]
		public static uint ReadUInt32LE(this Stream stream)
		{
			byte[] buffer = new byte[4];
			if (stream.Read(buffer, 0, 4) != 4)
				throw new EndOfStreamException();
			if (!BitConverter.IsLittleEndian)
				Array.Reverse(buffer, 0, 4);
			return BitConverter.ToUInt32(buffer, 0);
		}

		public static int ReadInt32LE(this Stream stream)
        {
			byte[] buffer = new byte[4];
			if (stream.Read(buffer, 0, 4) != 4)
                throw new EndOfStreamException();
            if (!BitConverter.IsLittleEndian)
                Array.Reverse(buffer, 0, 4);
            return BitConverter.ToInt32(buffer, 0);
        }

        public static ulong ReadUInt48BE(this Stream stream)
        {
			ulong result = 0;
            result = ReadUInt16BE(stream);
            result <<= 32;
            result += ReadUInt32BE(stream);
            return result;
        }

		public static long ReadInt64LE(this Stream stream)
		{
			byte[] buffer = new byte[8];
			if (stream.Read(buffer, 0, 8) != 8)
				throw new EndOfStreamException();
			if (!BitConverter.IsLittleEndian)
				Array.Reverse(buffer, 0, 4);
			return BitConverter.ToInt64(buffer, 0);
		}

		public static ulong ReadUInt64LE(this Stream stream)
		{
			byte[] buffer = new byte[8];
			if (stream.Read(buffer, 0, 8) != 8)
				throw new EndOfStreamException();
			if (!BitConverter.IsLittleEndian)
				Array.Reverse(buffer, 0, 4);
			return BitConverter.ToUInt64(buffer, 0);
		}

		public static ulong ReadUInt64BE(this Stream stream)
        {
			byte[] buffer = new byte[8];
			if (stream.Read(buffer, 0, 8) != 8)
                throw new EndOfStreamException();
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer, 0, 4);
            return BitConverter.ToUInt64(buffer, 0);
        }

        [DebuggerStepThrough]
        public static byte[] ReadBytes(this Stream stream, long length)
        {
            if (length > Int32.MaxValue)
                throw new ArgumentOutOfRangeException();
            if (length == 0)
                return new byte[0];
            if (length < 0)
                throw new ArgumentOutOfRangeException();

            int length32 = (int)length;

            byte[] buffer = new byte[length32];
            if (stream.Read(buffer, 0, length32) != length32)
                throw new EndOfStreamException();

            return buffer;
        }

        [DebuggerStepThrough]
        public static sbyte[] ReadSBytes(this Stream stream, long length)
        {
	        if (length > Int32.MaxValue)
		        throw new ArgumentOutOfRangeException();
	        if (length == 0)
		        return new sbyte[0];
	        if (length < 0)
		        throw new ArgumentOutOfRangeException();

	        int length32 = (int)length;

	        sbyte[] buffer = new sbyte[length32];
	        for (int i = 0; i < length32; i++)
	        {
		        buffer[i] = (sbyte)stream.ReadUInt8();
	        }

	        return buffer;
        }

        [DebuggerStepThrough]
        public static byte ReadUInt8(this Stream stream)
        {
			byte[] buffer = new byte[1];
			if (stream.Read(buffer, 0, 1) != 1)
                throw new EndOfStreamException();

            return buffer[0];
        }

        [DebuggerStepThrough]
        public static long GetAvailableBytes(this Stream stream)
        {
	        if (stream is GZipStream)
	        {
				GZipStream gzip = (GZipStream)stream;
				long baseStreamPosition = gzip.BaseStream.Length - gzip.BaseStream.Position;
				return baseStreamPosition;
	        }
            return stream.Length - stream.Position;
        }

        public static void WriteUInt8Repeat(this Stream stream, byte uint8, int repetitions)
        {
            byte[] buffer = new byte[repetitions];
            Array.Fill(buffer, uint8);
            stream.Write(buffer, 0, repetitions);
        }

        public static uint ReadUInt24BE(this Stream stream)
        {
            uint result = stream.ReadUInt16BE();
            result <<= 8;
            result += stream.ReadUInt8();
            return result;
        }

        public static double ReadDoubleLE(this Stream stream)
        {
			byte[] buffer = new byte[8];
			if (stream.Read(buffer, 0, 8) != 8)
		        throw new EndOfStreamException();
	        if (!BitConverter.IsLittleEndian)
		        Array.Reverse(buffer, 0, 8);
	        return BitConverter.ToDouble(buffer, 0);
		}

        public static double ReadDoubleBE(this Stream stream)
        {
            byte[] buffer = new byte[8];
            if (stream.Read(buffer, 0, 8) != 8)
                throw new EndOfStreamException();
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer, 0, 8);
            return BitConverter.ToDouble(buffer, 0);
        }

        public static void WriteUInt8(this Stream stream, byte value)
        {
	        byte[] buffer = new byte[1];
            buffer[0] = value;
            stream.Write(buffer, 0, 1);
        }

        public static void WriteUInt16BE(this Stream stream, ushort value)
        {
	        byte[] buffer = BitConverter.GetBytes(value);
	        if (BitConverter.IsLittleEndian)
		        (buffer[0], buffer[1]) = (buffer[1], buffer[0]);
	        stream.Write(buffer, 0, 2);
        }
		public static void WriteUInt16LE(this Stream stream, ushort value)
		{
			byte[] buffer = BitConverter.GetBytes(value);
			if (!BitConverter.IsLittleEndian)
				(buffer[0], buffer[1]) = (buffer[1], buffer[0]);
			stream.Write(buffer, 0, 2);
		}

		public static void WriteInt32BE(this Stream stream, int value)
		{
			byte[] buffer = BitConverter.GetBytes(value);
			if (BitConverter.IsLittleEndian)
				(buffer[0], buffer[1], buffer[2], buffer[3]) = (buffer[3], buffer[2], buffer[1], buffer[0]);
			stream.Write(buffer, 0, 4);
		}

		public static void WriteUInt32BE(this Stream stream, uint value)
        {
	        byte[] buffer = BitConverter.GetBytes(value);
	        if (BitConverter.IsLittleEndian)
		        (buffer[0], buffer[1], buffer[2], buffer[3]) = (buffer[3], buffer[2], buffer[1], buffer[0]);
	        stream.Write(buffer, 0, 4);
		}
		public static void WriteUInt32LE(this Stream stream, uint value)
		{
			byte[] buffer = BitConverter.GetBytes(value);
			if (!BitConverter.IsLittleEndian)
				(buffer[0], buffer[1], buffer[2], buffer[3]) = (buffer[3], buffer[2], buffer[1], buffer[0]);
			stream.Write(buffer, 0, 4);
		}

		public static void WriteInt32LE(this Stream stream, int value)
		{
			byte[] buffer = BitConverter.GetBytes(value);
			if (!BitConverter.IsLittleEndian)
				(buffer[0], buffer[1], buffer[2], buffer[3]) = (buffer[3], buffer[2], buffer[1], buffer[0]);
			stream.Write(buffer, 0, 4);
		}

		public static void WriteInt64LE(this Stream stream, long value)
		{
			byte[] buffer = BitConverter.GetBytes(value);
			if (!BitConverter.IsLittleEndian)
				(buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]) = (buffer[7], buffer[6], buffer[5], buffer[4], buffer[3], buffer[2], buffer[1], buffer[0]);
			stream.Write(buffer, 0, 8);
		}

		public static string ReadUTF8FixedLength(this Stream stream, int length)
        {
            byte[] buffer = new byte[length];
            if (stream.Read(buffer, 0, length) != length)
                throw new EndOfStreamException();
            return Encoding.UTF8.GetString(buffer);
        }

        public static string ReadUTF16NullTerminated(this Stream stream)
        {
            StringBuilder sb = new StringBuilder();
            do
            {
                char c = (char)stream.ReadUInt16LE();
                if (c == 0)
                    return sb.ToString();
                sb.Append(c);
            } while (stream.Position != stream.Length);
            return sb.ToString();
        }

		[DebuggerStepThrough]
		public static string ReadAsciiNullTerminated(this Stream stream)
        {
            StringBuilder sb = new StringBuilder();
            do
            {
                char c = (char)stream.ReadUInt8();
                if (c == 0)
                    return sb.ToString();
                sb.Append(c);
            } while(stream.Position != stream.Length);
            return sb.ToString();
        }

		/// <summary>
		/// Writes a variable-length integer to the stream.
        /// (code generated by ChatGPT, model GPT-4o)
		/// </summary>
		public static void WriteVarInt(this Stream stream, long value)
        {
			while ((value & ~0x7F) != 0) // More than 7 bits left?
			{
				stream.WriteByte((byte)((value & 0x7F) | 0x80)); // Write 7 bits + set highest bit
				value >>= 7; // Shift right by 7 bits
			}
			stream.WriteByte((byte)value); // Write last byte (highest bit unset)
		}

		/// <summary>
		/// Reads a variable-length integer from the stream.
		/// (code generated by ChatGPT, model GPT-4o)
		/// </summary>
		public static int ReadVarInt(this Stream stream)
		{
			int result = 0;
			int shift = 0;
			byte currentByte;

			do
			{
				currentByte = (byte)stream.ReadByte();
				result |= (currentByte & 0x7F) << shift; // Extract 7 bits and shift
				shift += 7;
			}
			while ((currentByte & 0x80) != 0); // Continue while highest bit is set

			return result;
		}

		public static double ReadFloat64LE(this Stream stream)
		{
			byte[] buffer = new byte[8];
			if (stream.Read(buffer, 0, 8) != 8)
				throw new EndOfStreamException();
			if (!BitConverter.IsLittleEndian)
				Array.Reverse(buffer);
			return BitConverter.ToDouble(buffer, 0);
		}

		public static void WriteFloat64LE(this Stream stream, double value)
		{
			byte[] buffer = BitConverter.GetBytes(value);
			if (!BitConverter.IsLittleEndian)
				Array.Reverse(buffer);
			stream.Write(buffer, 0, 8);
		}

		public static byte Peek(this Stream stream)
		{
			if (!stream.CanSeek)
				throw new NotSupportedException("Stream is not peekable");
			if (stream.Position == stream.Length)
				throw new EndOfStreamException();

			byte readUInt8 = stream.ReadUInt8();
			stream.Position--;
			return readUInt8;
		}

		public static void DumpToFile(this Stream stream, string filename)
		{
			FileStream fileStream = File.OpenWrite(filename);
			stream.CopyTo(fileStream);
			fileStream.Flush(true);
			fileStream.Close();
			fileStream.Dispose();
		}

		public static int TryReadExactly(this Stream stream, byte[] buffer, int offset, int count)
		{
			int totalRead = 0;

			while (totalRead < count)
			{
				int bytesRead = stream.Read(buffer, offset + totalRead, count - totalRead);
				if (bytesRead == 0)
				{
					// End of stream
					break;
				}

				totalRead += bytesRead;
			}
			return totalRead;
		}
	}
}
