using System.Collections.Generic;
using System;

using NeonTea.Quakeball.TeaNet.Peers;


namespace NeonTea.Quakeball.TeaNet.Packets {
    /// <summary>Manages a single form of conversation between clients for the Peer. Don't forget to register your packets with <see cref="Protocol.RegisterPacket(Type)"/></summary>
    public abstract class Protocol {
        private Dictionary<Type, int> PacketToId = new Dictionary<Type, int>();
        private Dictionary<int, Type> IdToPacket = new Dictionary<int, Type>();
        private int PacketIdCounter;

        /// <summary>Refers to the peer it is registered to</summary>
        public Peer Peer;

        /// <summary>Unique identifier for the protocol. This should be different for every protocol.</summary>
        public abstract byte Identifier { get; }
        /// <summary>Version of the protocol, should be changed if existing packets are changed, new packets are registered or old packets are removed.</summary>
        public abstract string Version { get; }

        /// <summary>Called when the Peer receives a packet from a connection that uses this protocol</summary>
        public abstract void Receive(Connection conn, Packet packet);
        /// <summary>Called when a ConnectionStatus is changed for a connection that uses this protocol</summary>
        public abstract void ConnectionStatusChanged(ConnectionStatus oldStatus, ConnectionStatus newStatus, Connection conn);
        /// <summary>Called when a connection that uses this protocol is timed out suddenly.</summary>
        public abstract void Timeout(Connection conn);

        /// <summary>Register a packet for sending and receiving.</summary>
        public int RegisterPacket(Type t) {
            if (t.BaseType != typeof(Packet) || PacketToId.ContainsKey(t)) {
                return -1;
            }
            int id = PacketIdCounter++;
            PacketToId.Add(t, id);
            IdToPacket.Add(id, t);
            return id;
        }

        public ByteBuffer BuildMessage(Connection connection) {
            ByteBuffer buffer = new ByteBuffer();
            foreach (byte b in Peer.Fingerprint) {
                buffer.Write(b);
            }
            buffer.Write(Identifier);
            if (connection.Status == ConnectionStatus.Establishing) {
                buffer.Write((byte)PacketStage.Establishing);
                buffer.Write(Version);
            } else if (connection.Status == ConnectionStatus.Closed) {
                buffer.Write((byte)PacketStage.Closed);
            } else if (connection.Status == ConnectionStatus.Rejected) {
                buffer.Write((byte)PacketStage.Rejected);
                buffer.Write((byte)connection.ClosingReason);
            } else if (connection.Status == ConnectionStatus.Ready) {
                buffer.Write((byte)PacketStage.Ready);
                buffer.Write(connection.Internal.LatestInwardReliable);
            }
            return buffer;
        }

        public int GetPacketTypeID(Packet packet) {
            return PacketToId[packet.GetType()];
        }

        public Type GetPacketType(int id) {
            return IdToPacket[id];
        }
    }
}