using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Skyscraper.Net.Sockets
{
    public static class NetworkStreamExtensions
    {
        public static void WriteInt32BE(this NetworkStream stream, int value)
        {
            byte[] buffer = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer);
            stream.Write(buffer, 0, 4);
        }

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

        public static void WriteUInt32BE(this NetworkStream stream, uint value)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
                Array.Reverse(bytes);
            stream.Write(bytes, 0, 4);
        }

        public static void WriteUTF8(this NetworkStream stream, string value)
        {
            stream.WriteUInt32BE(4);
            if (value == null)
            {
                stream.WriteBoolean(true);
                return;
            }
            else
            {
                stream.WriteBoolean(false);
            }
            byte[] bytes = Encoding.UTF8.GetBytes(value);
            stream.WriteUInt32BE((uint)bytes.Length);
            stream.Write(bytes, 0, bytes.Length);
        }

        public static string ReadUTF8(this NetworkStream stream)
        {
            uint magic = stream.ReadUInt32BE();
            if (magic != 4)
                throw new InvalidDataException("Expected an utf-8, but got something weird.");

            bool isNull = stream.ReadBoolean();
            if (isNull)
                return null;

            uint length = stream.ReadUInt32BE();
            byte[] result = stream.ReadBytes(length);
            return Encoding.UTF8.GetString(result);
        }

        public static void WriteGuid(this NetworkStream stream, Guid guid)
        {
            stream.WriteUInt32BE(5);
            stream.WriteByteArray(guid.ToByteArray());
        }

        public static byte[] ReadByteArray(this NetworkStream stream)
        {
            uint magic = stream.ReadUInt32BE();
            if (magic != 6)
                throw new IOException("excepted a byte[], but got junk");
            uint len = stream.ReadUInt32BE();
            byte[] buffer = new byte[len];
            if (stream.Read(buffer, 0, buffer.Length) != buffer.Length)
                throw new EndOfStreamException();
            return buffer;
        }

        public static void WriteByteArray(this NetworkStream stream, byte[] buffer)
        {
            stream.WriteUInt32BE(6);
            stream.WriteUInt32BE((uint)buffer.Length);
            stream.Write(buffer, 0, buffer.Length);
        }

        public static void WriteBoolean(this NetworkStream stream, bool value)
        {
            byte[] buffer = new byte[1] { 0 };
            if (value)
                buffer[0] = 1;
            stream.Write(buffer, 0, 1);
        }

        public static bool ReadBoolean(this NetworkStream stream)
        {
            byte[] buffer = new byte[1];
            if (stream.Read(buffer, 0, 1) != 1)
                throw new EndOfStreamException("failed to read boolean");
            switch (buffer[0])
            {
                case 0:
                    return false;
                case 1:
                    return true;
                default:
                    throw new EncoderFallbackException("expected to read 1 or 0, but got junk");
            }
        }

        public static void WriteInt64BE(this NetworkStream stream, long value)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
                Array.Reverse(bytes);
            stream.Write(bytes, 0, 8);
        }

        public static long ReadInt64BE(this NetworkStream stream)
        {
            byte[] buffer = new byte[8];
            if (stream.Read(buffer, 0, 8) != 8)
                throw new EndOfStreamException("failed to read int64");
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer);
            return BitConverter.ToInt64(buffer, 0);
        }

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

        public static byte ReadUInt8(this NetworkStream stream)
        {
            byte[] buffer = new byte[1];
            if (stream.Read(buffer,0,1) != 1)
                throw new EndOfStreamException("failed to read uint8");
            return buffer[0];
        }

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

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

        public static void WriteFloatBE(this NetworkStream stream, float value)
        {
            byte[] buffer = BitConverter.GetBytes(value);
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer);
            stream.Write(buffer, 0, 4);
        }

        public static float ReadFloatBE(this NetworkStream stream)
        {
            byte[] buffer = new byte[4];
            if (stream.Read(buffer, 0, 4) != 4)
                throw new EndOfStreamException("failed to read float");
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer);
            return BitConverter.ToSingle(buffer, 0);
        }

        public static void WriteIntPtr(this NetworkStream stream, IntPtr ptr)
        {
            stream.WriteUInt32BE(7);
            stream.WriteInt32BE(ptr.ToInt32());
            stream.WriteInt64BE(ptr.ToInt64());
        }

        public static IntPtr ReadIntPtr(this NetworkStream stream)
        {
            uint magic = stream.ReadUInt32BE();
            if (magic != 7)
                throw new InvalidDataException("expected intPtr but got junk");

            int ptr32 = stream.ReadInt32BE();
            long ptr64 = stream.ReadInt64BE();
            if (Environment.Is64BitProcess)
                return new IntPtr(ptr64);
            else
                return new IntPtr(ptr32);
        }

        public static void WriteIPEndPoint(this NetworkStream stream, IPEndPoint ptr)
        {
            stream.WriteUInt32BE(8);
            stream.WriteByteArray(ptr.Address.GetAddressBytes());
            stream.WriteInt32BE(ptr.Port);
        }

        public static IPEndPoint ReadIPEndPoint(this NetworkStream stream)
        {
            uint magic = stream.ReadUInt32BE();
            if (magic != 8)
                throw new InvalidDataException("expected IPEndPoint but got junk");

            byte[] adrBuffer = stream.ReadByteArray();
            int port = stream.ReadInt32BE();

            IPAddress addr = new IPAddress(adrBuffer);
            IPEndPoint result = new IPEndPoint(addr, port);
            return result;
        }

        public static void WriteVersion(this NetworkStream stream, Version version)
        {
            stream.WriteUInt32BE(9);
            stream.WriteInt32BE(version.Major);
            stream.WriteInt32BE(version.Minor);
            stream.WriteInt32BE(version.Build);
            stream.WriteInt32BE(version.Revision);
        }

        public static Version ReadVersion(this NetworkStream stream)
        {
			uint magic = stream.ReadUInt32BE();
			if (magic != 9)
				throw new InvalidDataException("expected Version but got junk");

            int major = stream.ReadInt32BE();
            int minor = stream.ReadInt32BE();
            int build = stream.ReadInt32BE();
            int revision = stream.ReadInt32BE();

            return new Version(major,minor,build,revision);
		}
    }
}
