using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NeonTea.Quakeball.Networking.Packets;
using NeonTea.Quakeball.Players;
using NeonTea.Quakeball.Game;
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 ulong PlayerIdCounter;

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

        public GameMaster GameMaster;

        public Server() {
            LocalPlayer = new NetPlayer(ulong.MaxValue);
        }

        public override void Start(string address, int port, string nick, PeerMessageListener listener) {
            if (Peer.Running) {
                return;
            }
            Players.Clear();
            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>();

            GameMaster = new GameMaster();

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

            GameMaster.PlayerJoined(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;
                }

                NicknamePckt nick = new NicknamePckt(p.Nick, p.Id);
                SpawnPckt spawn = new SpawnPckt(p.Controlled.transform.position, p.Id);
                PlayerInitPckt init = new PlayerInitPckt(p.Id, p.Team, nick, spawn);
                Peer.SendReliableLater(conn.uid, init);
            }
            NetPlayer RemotePlayer = new NetPlayer(PlayerIdCounter++);
            GameMaster.PlayerJoined(RemotePlayer);
            AddPlayer(RemotePlayer);

            SelfIdentPckt ident = new SelfIdentPckt(RemotePlayer.Id, RemotePlayer.Team);
            Peer.SendReliable(conn.uid, ident);
        }

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

        public void PlayerJoined(NetPlayer player) {
            NicknamePckt nick = new NicknamePckt(player.Nick, player.Id);
            SpawnPckt spawn = new SpawnPckt(player.Controlled.transform.position, player.Id);
            PlayerInitPckt init = new PlayerInitPckt(player.Id, player.Team, nick, spawn);
            SendReliableToAll(init, except: spawn.PlayerId);
        }

        public override void Handle(Connection conn, Packet packet) {
            if (packet is ReadyPckt) {
                if (Players[conn.uid].Controlled == null) {
                    NetPlayer player = Players[conn.uid];
                    Player obj = Net.SpawnPlayer(GameMaster.GetSpawn(player)).GetComponent<Player>();
                    player.Controlled = obj;

                    SpawnPckt spawn = new SpawnPckt(obj.transform.position, player.Id);
                    Peer.SendReliable(conn.uid, spawn);

                    PlayerJoined(player);
                }
            } else if (packet is NicknamePckt) {
                NicknamePckt nick = (NicknamePckt)packet;
                Players[conn.uid].Nick = nick.Nick;
                nick.PlayerId = conn.uid;
                SendReliableToAll(nick);
            } else if (packet is PlayerUpdatePckt) {
                PlayerUpdatePckt updatePckt = (PlayerUpdatePckt)packet;
                HandleUpdatePckt(conn.uid, 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;

                PingListPckt pinglist = new PingListPckt(PlayerList);
                SendReliableToAll(pinglist);
            }
        }

        private void HandleUpdatePckt(ulong uid, PlayerUpdatePckt pckt) {
            if (Players[uid].Controlled != null && !Players[uid].Controlled.IsDead) {
                pckt.PlayerId = uid;
                Players[uid].Controlled.ProcessUpdatePacket(pckt);
            }
        }

        private void HandleSyncPckt(ulong uid, PlayerSyncPacket pckt) {
            if (Players[uid].Controlled != null && !Players[uid].Controlled.IsDead) {
                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;
                case PlayerAction.Shoot:
                    ShootData shootData = (ShootData)action.Serializable;
                    HandleSyncPckt(uid, shootData.SyncPckt);
                    Players[uid].Controlled.Shoot();

                    action.PlayerId = uid;
                    NetPlayer Player = Players[uid];
                    shootData.SyncPckt = Player.Controlled.CreateSyncPacket(Player.Id, Player.Unsynced);
                    Player.Unsynced = false;
                    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, Serializable serializable = null) {
            PlayerActionPckt actionPckt = new PlayerActionPckt(action, serializable, LocalPlayer.Id);
            SendReliableToAll(actionPckt);
        }

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

        public void SendHit(ulong source, ulong target) {
            HitPckt hit = new HitPckt(source, target);
            SendReliableToAll(hit);
        }

        public void HandlePlayerDeath(ulong deadid, ulong killerid) {
            Players[deadid].Controlled.Dead(killerid);
            DeadPckt dead = new DeadPckt(deadid, killerid);
            SendReliableToAll(dead);
        }

        public void HandlePlayerRespawn(ulong uid) {
            Vector3 point = GameMaster.GetSpawn(Players[uid]);
            Players[uid].Controlled.Respawn(point);
            SpawnPckt spawn = new SpawnPckt(point, uid, false);
            SendReliableToAll(spawn);

        }
    }
}