using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NeonTea.Quakeball.Networking.Packets;
using NeonTea.Quakeball.Players;
using NeonTea.Quakeball.Interface;
using NeonTea.Quakeball.TeaNet.Peers;
using NeonTea.Quakeball.TeaNet.Packets;

namespace NeonTea.Quakeball.Networking.Instances {
    public class Server : NetInstance {

        private NetChaperone Net;

        private Dictionary<ulong, NetPlayer> Players = new Dictionary<ulong, NetPlayer>();
        public List<NetPlayer> PlayerList { get; private set; } = new List<NetPlayer>();
        private ulong PlayerIdCounter;

        private NetPlayer LocalPlayer = new NetPlayer(ulong.MaxValue);

        private byte LastPingIdent;
        private float LastSentPing;
        public static float PingInterval = 1;

        public override void Start(string address, int port, PeerMessageListener listener) {
            if (Peer.Running) {
                return;
            }
            Peer = new Peer(Fingerprint);
            Peer.MessageListener = listener;
            Peer.Start(port);
            Peer.RegisterProtocol(new GameProtocol(this));
            Peer.StartListen(address, port);

            Net = GameObject.FindGameObjectWithTag("Net").GetComponent<NetChaperone>();

            Player obj = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();
            LocalPlayer.Controlled = obj;
            AddPlayer(LocalPlayer);
        }

        public override void OnStop() {
            foreach (NetPlayer p in PlayerList) {
                Peer.Disconnect(p.Id);
                if (p.Controlled != null && p.Id != LocalPlayer.Id) {
                    GameObject.Destroy(p.Controlled.gameObject);
                }
            }
            PlayerList.Clear();
            Players.Clear();
        }

        public override void Connected(Connection conn) {
            Terminal.Singleton.Println($"{conn.uid} Connected");
            foreach (NetPlayer p in Players.Values) {

                if (p.Controlled == null) { // Not yet initialized, sending later.
                    continue;
                }
                SpawnPckt spawn = new SpawnPckt();
                spawn.PlayerId = p.Id;
                spawn.Location = p.Controlled.transform.position;
                Peer.SendReliableLater(conn.uid, spawn);
            }
            NetPlayer RemotePlayer = new NetPlayer(PlayerIdCounter++);
            AddPlayer(RemotePlayer);
            SelfIdentPckt ident = new SelfIdentPckt();
            ident.PlayerId = RemotePlayer.Id;
            Peer.SendReliable(conn.uid, ident);
        }

        public override void Disconnected(Connection conn) {
            Terminal.Singleton.Println($"{conn.uid} Disconnected: {conn.ClosingReason}");
            if (Players.ContainsKey(conn.uid)) {
                GameObject.Destroy(Players[conn.uid].Controlled.gameObject);
                RemovePlayer(Players[conn.uid]);
            }
        }

        public override void Handle(Connection conn, Packet packet) {
            if (packet is SpawnPckt) {
                SpawnPckt spawn = (SpawnPckt)packet;
                if (Players[conn.uid].Controlled == null) {
                    Player obj = Net.SpawnPlayer(spawn.Location).GetComponent<Player>();
                    Players[conn.uid].Controlled = obj;

                    spawn = new SpawnPckt();
                    spawn.PlayerId = conn.uid;
                    spawn.Location = obj.transform.position;
                    SendReliableToAll(spawn, except: spawn.PlayerId);
                }
            } else if (packet is PlayerUpdatePckt) {
                PlayerUpdatePckt updatePckt = (PlayerUpdatePckt)packet;
                if (Players[conn.uid].Controlled != null) {
                    updatePckt.PlayerId = conn.uid;
                    Players[conn.uid].Controlled.ProcessUpdatePacket(updatePckt);
                }
            } else if (packet is PlayerSyncPacket) {
                PlayerSyncPacket syncPckt = (PlayerSyncPacket)packet;
                HandleSyncPckt(conn.uid, syncPckt);
            } else if (packet is PlayerActionPckt) {
                PlayerActionPckt action = (PlayerActionPckt)packet;
                if (Players[conn.uid].Controlled != null) {
                    HandleAction(conn.uid, action);
                }
            } else if (packet is PingPckt) {
                PingPckt ping = (PingPckt)packet;
                if (!ping.ServerReceived) {
                    ping.ServerReceived = true;
                    Peer.SendReliable(conn.uid, ping);
                    NetPlayer p = Players[conn.uid];
                    if (ping.Identifier == LastPingIdent) {
                        p.Ping = Time.time - LastSentPing;
                    } else {
                        p.Ping = PingInterval + 0.001f;
                    }
                    if (p.Controlled != null) {
                        p.Controlled.PingBias = p.Ping;
                    }
                }
            }
        }

        public override void Update() {
            if (Time.time > LastSentPing + PingInterval) {
                LastPingIdent = (byte)((LastPingIdent + 1) % 200);
                PingPckt ping = new PingPckt(LastPingIdent);
                SendReliableToAll(ping);
                LastSentPing = Time.time;
            }
        }

        private void AddPlayer(NetPlayer player) {
            Players.Add(player.Id, player);
            PlayerList.Add(player);
        }

        private void RemovePlayer(NetPlayer player) {
            Players.Remove(player.Id);
            PlayerList.Remove(player);
        }

        private void HandleSyncPckt(ulong uid, PlayerSyncPacket pckt) {
            if (Players[uid].Controlled != null) {
                pckt.PlayerId = uid;
                if (!Players[uid].Controlled.ProcessSyncPacket(pckt, false)) {
                    Players[uid].Unsynced = true;
                }
            }
        }

        private void HandleAction(ulong uid, PlayerActionPckt action) {
            switch (action.Action) {
                case PlayerAction.Jump:
                    if (Players[uid].Controlled.Jump()) {
                        action.PlayerId = uid;
                        SendReliableToAll(action);
                    }
                    break;
            }
        }

        public void SendReliableToAll(Packet packet, ulong except = ulong.MaxValue) {
            foreach (NetPlayer p in Players.Values) {
                if (p.Id == ulong.MaxValue || p.Id == except) {
                    continue;
                }
                Peer.SendReliable(p.Id, packet);
            }
        }

        public void SendUnreliableToAll(Packet packet, ulong except = ulong.MaxValue) {
            foreach (NetPlayer p in Players.Values) {
                if (p.Id == ulong.MaxValue || p.Id == except) {
                    continue;
                }
                Peer.SendUnreliable(p.Id, packet);
            }
        }

        public override void UpdateLocalPlayer() {
            MultiplePlayerUpdatesPckt pckt = new MultiplePlayerUpdatesPckt(PlayerList);
            SendUnreliableToAll(pckt);
        }

        public override void LocalPlayerAction(PlayerAction action) {
            PlayerActionPckt jump = new PlayerActionPckt(action, LocalPlayer.Id);
            SendReliableToAll(jump);
        }

        public override void SendPlayerSync() {
            MultipleSyncsPckt pckt = new MultipleSyncsPckt(PlayerList);
            SendUnreliableToAll(pckt);
        }
    }
}