using InABox.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace InABox.WebSocket.Shared
{
    public class ByteReader
    {
        public byte[] Data;
        public int Index;

        public ByteReader(byte[] data)
        {
            Data = data;
            Index = 0;
        }

        public Guid ReadGuid()
        {
            var guid = new Guid(new ReadOnlySpan<byte>(Data, Index, 16));
            Index += 16;
            return guid;
        }

        public string ReadUTF8String()
        {
            int i = Index;
            while (i < Data.Length && Data[i] != 0)
            {
                i++;
            }
            var str = Encoding.UTF8.GetString(Data, Index, i - Index);
            Index = i + 1;
            return str;
        }
        public byte ReadByte()
        {
            return Data[Index++];
        }

        public ByteReader Skip(int count)
        {
            Index += count;
            return this;
        }
    }

    public class ByteWriter
    {
        private Stream Stream;

        public ByteWriter(Stream stream)
        {
            Stream = stream;
        }

        public ByteWriter WriteByte(byte b)
        {
            Stream.WriteByte(b);
            return this;
        }
        public ByteWriter WriteGuid(Guid guid)
        {
            Stream.Write(guid.ToByteArray());
            return this;
        }

        /// <summary>
        /// Writes a null-terminated UTF-8 encoded string to the buffer
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public ByteWriter WriteUTF8String(string str)
        {
            Stream.Write(Encoding.UTF8.GetBytes(str));
            Stream.WriteByte(0);
            return this;
        }
    }

    public abstract class SocketMessage
    {
        public enum MessageType
        {
            Initial = 0x00,
            Push = 0x01
        }

        public abstract MessageType Type { get; }

        protected abstract void Write(ByteWriter writer);

        public void Write(Stream stream)
        {
            var writer = new ByteWriter(stream)
                .WriteByte((byte)Type);
            Write(writer);
        }

        public byte[] WriteToBytes()
        {
            using(var stream = new MemoryStream())
            {
                Write(stream);
                return stream.GetBuffer().ToArray();
            }
        }

        public static SocketMessage? ReadMessage(byte[] data)
        {
            try
            {
                var type = (MessageType)data[0];
                var byteReader = new ByteReader(data).Skip(1);
                switch (type)
                {
                    case MessageType.Initial:
                        return InitialMessage.ReadMessage(byteReader);
                    case MessageType.Push:
                        return PushMessage.ReadMessage(byteReader);
                }
            }
            catch (Exception)
            {
                return null;
            }
            return null;
        }
    }

    public class InitialMessage : SocketMessage
    {
        public override MessageType Type => MessageType.Initial;

        public Guid SessionID { get; set; }

        public Platform Platform { get; set; }

        public InitialMessage(Guid sessionID, Platform platform)
        {
            SessionID = sessionID;
            Platform = platform;
        }

        protected override void Write(ByteWriter writer)
        {
            writer.WriteGuid(SessionID)
                .WriteByte((byte)Platform);
        }

        public static InitialMessage ReadMessage(ByteReader data)
        {
            var session = data.ReadGuid();
            var platform = (Platform)data.ReadByte();
            return new InitialMessage(session, platform);
        }
    }

    public class PushMessage : SocketMessage
    {
        public override MessageType Type => MessageType.Push;

        public string EntityType;

        public string EntityData;

        private PushMessage(string entityType, string entityData)
        {
            EntityType = entityType;
            EntityData = entityData;
        }

        protected override void Write(ByteWriter writer)
        {
            writer.WriteUTF8String(EntityType);
            writer.WriteUTF8String(EntityData);
        }

        public static PushMessage ReadMessage(ByteReader reader)
        {
            var type = reader.ReadUTF8String();
            var data = reader.ReadUTF8String();
            return new PushMessage(type, data);
        }

        public static PushMessage Push<TPush>(TPush push)
            where TPush : BaseObject
        {
            return new PushMessage(typeof(TPush).EntityName(), Serialization.Serialize(push));
        }
        public static PushMessage Push(Type TPush, object push)
        {
            return new PushMessage(TPush.EntityName(), Serialization.Serialize(push));
        }
    }
}
