using System.Collections.Generic;
using System;
using System.Text;
namespace NeonTea.Quakeball.TeaNet.Packets {
    /// Contains a stream of bytes for sending or receiving over the internet.
    public class ByteBuffer {
        public int Size => Bytes.Count;
        private List Bytes;
        private int pos = 0;
        /// Creates a new empty ByteBuffer
        public ByteBuffer() {
            Bytes = new List();
        }
        /// Creates a new ByteBuffer from the given byte[]
        public ByteBuffer(byte[] bytes) {
            Bytes = new List(bytes);
        }
        /// Packs the byte buffer in to a byte[] array.
        public byte[] Pack() {
            return Bytes.ToArray();
        }
        /// is there a byte to read next?
        public bool CanRead() {
            return pos < Bytes.Count;
        }
        /// Reads a given object with Serializable implemented. Assumes there is one next.
        public void ReadSerializable(Serializable s) {
            s.Read(this);
        }
        /// Read a char on the buffer. Assumes there is one next.
        public char ReadChar() {
            return BitConverter.ToChar(Read(2), 0);
        }
        /// Read a boolean on the buffer. Assumes there is one next.
        public bool ReadBool() {
            return Read() == 1;
        }
        /// Read a double on the buffer. Assumes there is one next.
        public double ReadDouble() {
            return BitConverter.ToDouble(Read(8), 0);
        }
        /// Read a float on the buffer. Assumes there is one next.
        public float ReadFloat() {
            return BitConverter.ToSingle(Read(4), 0);
        }
        /// Read an unsigned long on the buffer. Assumes there is one next.
        public ulong ReadULong() {
            return BitConverter.ToUInt64(Read(8), 0);
        }
        /// Read an unsigned integer on the buffer. Assumes there is one next.
        public uint ReadUInt() {
            return BitConverter.ToUInt32(Read(4), 0);
        }
        /// Read an unsigned short on the buffer. Assumes there is one next.
        public ushort ReadUShort() {
            return BitConverter.ToUInt16(Read(2), 0);
        }
        /// Read a long on the buffer. Assumes there is one next.
        public long ReadLong() {
            return BitConverter.ToInt64(Read(8), 0);
        }
        /// Read an integer on the buffer. Assumes there is one next.
        public int ReadInt() {
            return BitConverter.ToInt32(Read(4), 0);
        }
        /// Read a short on the buffer. Assumes there is one next.
        public short ReadShort() {
            return BitConverter.ToInt16(Read(2), 0);
        }
        /// Read a string on the buffer. Assumes there is one next.
        public string ReadString() {
            int length = ReadInt();
            string s = Encoding.UTF8.GetString(Read(length));
            return s;
        }
        /// Read an integer on the buffer. Assumes there is one next.
        public byte[] Read(int amount) {
            byte[] bytes = Bytes.GetRange(pos, amount).ToArray();
            pos += amount;
            return bytes;
        }
        /// Read the next byte on the buffer. Assumes there is one next.
        public byte Read() {
            return Bytes[pos++];
        }
        /// Write something that implements Serializable.
        public void Write(Serializable s) {
            s.Write(this);
        }
        /// Write a char to the buffer.
        public void Write(char c) {
            Bytes.AddRange(BitConverter.GetBytes(c));
        }
        /// Write a boolean to the buffer.
        public void Write(bool b) {
            Write(b ? (byte)0b1 : (byte)0b0);
        }
        /// Write a double to the buffer.
        public void Write(double d) {
            Bytes.AddRange(BitConverter.GetBytes(d));
        }
        /// Write a float to the buffer.
        public void Write(float f) {
            Bytes.AddRange(BitConverter.GetBytes(f));
        }
        /// Write an unsigned long to the buffer.
        public void Write(ulong l) {
            Bytes.AddRange(BitConverter.GetBytes(l));
        }
        /// Write an unsigned integer to the buffer.
        public void Write(uint i) {
            Bytes.AddRange(BitConverter.GetBytes(i));
        }
        /// Write an unsigned short to the buffer.
        public void Write(ushort s) {
            Bytes.AddRange(BitConverter.GetBytes(s));
        }
        /// Write a long to the buffer.
        public void Write(long l) {
            Bytes.AddRange(BitConverter.GetBytes(l));
        }
        /// Write an integer to the buffer.
        public void Write(int i) {
            Bytes.AddRange(BitConverter.GetBytes(i));
        }
        /// Write a short to the buffer.
        public void Write(short s) {
            Bytes.AddRange(BitConverter.GetBytes(s));
        }
        /// Write a string to the buffer.
        public void Write(string s) {
            byte[] bytes = Encoding.UTF8.GetBytes(s);
            Write(bytes.Length);
            Bytes.AddRange(bytes);
        }
        /// Write a byte to the buffer.
        public void Write(byte b) {
            Bytes.Add(b);
        }
        /// Read weather the given fingerprint is next on the buffer.
        public bool ReadFingerprint(byte[] fingerprint) {
            foreach (byte b in fingerprint) {
                if (!(CanRead() && Read() == b)) {
                    return false;
                }
            }
            return true;
        }
        public PacketStage ReadStage() {
            PacketStage stage = PacketStage.Closed;
            switch (Read()) {
                case 0:
                    stage = PacketStage.Establishing;
                    break;
                case 1:
                    stage = PacketStage.Rejected;
                    break;
                case 2:
                    stage = PacketStage.Closed;
                    break;
                case 3:
                    stage = PacketStage.Ready;
                    break;
            }
            return stage;
        }
        public ClosingReason ReadClosingReason() {
            ClosingReason reason = ClosingReason.Unknown;
            switch (Read()) {
                case 0:
                    reason = ClosingReason.Unknown;
                    break;
                case 1:
                    reason = ClosingReason.IncorrectVersion;
                    break;
            }
            return reason;
        }
        /// Write an entire packet using the protocol to the buffer.
        public void WritePacket(Protocol protocol, Packet p) {
            int old = Bytes.Count;
            Write(protocol.GetPacketTypeID(p));
            p.WriteMeta(this);
            p.Write(this);
            p.Size = Bytes.Count - old;
        }
        /// Read an entire packet using the given protocol from the buffer.
        public Packet ReadPacket(Protocol protocol) {
            int old = pos;
            int packetType = ReadInt();
            Type t = protocol.GetPacketType(packetType);
            Packet p = (Packet)Activator.CreateInstance(t);
            p.ReadMeta(this);
            p.Read(this);
            p.Size = pos - old;
            return p;
        }
    }
}