using System.Collections.Generic;
using UnityEngine;
using System;
using System.Net;
using System.Net.Sockets;
using NeonTea.Quakeball.TeaNet.Packets;

namespace NeonTea.Quakeball.TeaNet.Peers {
    /// <summary>Main class for networking. Remember to register a protocol before using. Remember to call Update from a gameobject!</summary>
    public class Peer : PeerMessageListener {
        /// <summary>Underlying UdpClient. Do not touch unless you know what you are doing.</summary>
        public UdpClient UdpClient { get; private set; }

        /// <summary>The fingerprint for networking. Used to make sure incoming bytes are from a correct source.</summary>
        public byte[] Fingerprint { get; private set; }
        /// <summary>Shorthand for ConnectionManager.Timeout: The amount of milliseconds before a connection is timed out.</summary>
        public long Timeout {
            get {
                return ConnectionManager.Timeout;
            }
            set {
                ConnectionManager.Timeout = value;
            }
        }
        /// <summary>Shorthand for ConnectionManager.Interval: The interval of updates and rate of re-sending reliable messages.</summary>
        public long UpdateInterval {
            get {
                return ConnectionManager.Interval;
            }
            set {
                ConnectionManager.Interval = value;
            }
        }
        /// <summary>Whether the Peer is currently doing anything or not.null</sumary>
        public bool Running { get; private set; }

        public ListenerThread ListenerThread;
        public ConnectionManager ConnectionManager;
        public Dictionary<byte, Protocol> RegisteredProtocols = new Dictionary<byte, Protocol>();

        /// <summary>Listener for messages and errors from within the Peer.</summary>
        public PeerMessageListener MessageListener;

        /// <summary>Creates a new Peer with the given fingerprint. The fingerprint can be anything, it just must be same on both peers.</summary>
        public Peer(byte[] fingerprint) {
            Fingerprint = fingerprint;
            ConnectionManager = new ConnectionManager(this);
            MessageListener = this;
        }

        /// <summary>Starts the UdpClient, but does no networking as is.</summary>
        public void Start(int sending_port) {
            if (Running) {
                return;
            }
            UdpClient = new UdpClient(sending_port);
            MessageListener.Message("UdpClient Started");
            Running = true;
        }

        /// <summary>Abruptly stops the UdpClient and all relevant threads.</summary>
        public void Stop() {
            ConnectionManager.StopThread();
            if (ListenerThread != null) {
                ListenerThread.Stop();
            }
            UdpClient.Dispose();
            UdpClient.Close();
            Running = false;
        }

        /// <summary>Start listening to a given address and port. Usually 0.0.0.0, 0 for clients and 0.0.0.0, port for servers</summary>
        public void StartListen(string address, int port) {
            IPEndPoint endpoint = new IPEndPoint(FindAddress(address), port);
            StartListen(endpoint);
        }

        private void StartListen(IPEndPoint endpoint) {
            if (ListenerThread != null) {
                return; // Cant listen twice
            }
            ListenerThread = new ListenerThread(this, endpoint);
            ListenerThread.Start();
            MessageListener.Message($"Started listening to {endpoint}");
        }

        /// <summary>Connect to a remote host. Will initialize a listener to 0.0.0.0:0 if no listener is started.</summary>
        public void Connect(string address, int port, byte protocolIdent, bool startListening = true) {
            if (startListening) {
                IPEndPoint listenEndpoint = (IPEndPoint)UdpClient.Client.LocalEndPoint;
                StartListen(listenEndpoint);
            }
            IPEndPoint endpoint = new IPEndPoint(FindAddress(address), port);
            ConnectionManager.StartConnection(endpoint, protocolIdent);
            MessageListener.Message($"Connecting to {endpoint}");
        }

        /// <summary>Soft-disconnects the connection with the given uid, meaning it will wait until they acknowledge, before timing out.abstract</summary>
        public void Disconnect(ulong uid) {
            ConnectionManager.CloseConnection(uid, ClosingReason.Manual);
        }

        /// <summary>Send a reliable packet, meaning it will reliably be delivered.</summary>
        public void SendReliable(ulong uid, Packet packet) {
            if (Running) {
                ConnectionManager.AddPacketToQueue(uid, packet);
                ConnectionManager.SendPacketQueue(uid);
            }
        }

        /// <summary>Add reliable packet to queue, so that it will be sent on the next update.</summary>
        public void SendReliableLater(ulong uid, Packet packet) {
            if (Running) {
                ConnectionManager.AddPacketToQueue(uid, packet);
            }
        }

        /// <summary>Send an unreliable packet, meaning its delivery is not reliable.</summary>
        public void SendUnreliable(ulong uid, Packet packet) {
            if (Running) {
                ConnectionManager.SendSingleUnreliable(uid, packet);
            }
        }

        /// <summary>Get a Connection instance from the given uid, if such exists. Null otherwise.</summary>
        public Connection GetConnection(ulong uid) {
            return ConnectionManager.GetConnection(uid);
        }

        /// <summary>Register a given protocol. Returns protocol.Identifier if successful, 0 otherwise.</summary>
        public byte RegisterProtocol(Protocol protocol) {
            byte ident = protocol.Identifier;
            if (RegisteredProtocols.ContainsKey(ident)) {
                return 0;
            }
            RegisteredProtocols.Add(ident, protocol);
            ConnectionManager.ProtocolActionQueues.Add(ident, new Queue<ProtocolAction>());
            protocol.Peer = this;
            return ident;
        }

        /// <summary>Get protocol instance from the given identifier, if such exists.</summary>
        public Protocol GetProtocol(byte ident) {
            if (RegisteredProtocols.ContainsKey(ident)) {
                return RegisteredProtocols[ident];
            }
            return null;
        }

        /// <summary>Shorthand for Peer.ConnectionManager.Update(): Handles network stuff that was received since last update.</summary>
        public void Update() {
            ConnectionManager.Update();
        }

        public void Message(string msg) { }
        public void Err(string msg) { }

        private IPAddress FindAddress(string host) {
            IPAddress addr;
            try {
                addr = Dns.GetHostAddresses(host)[0];
            } catch (ArgumentException) {
                addr = IPAddress.Parse(host);
            }
            return addr;
        }
    }

    /// <summary>Listener for messages and errors from the Peer.</summary>
    public interface PeerMessageListener {
        void Message(string msg);
        void Err(string msg);
    }
}
